/*-------------------------------------------------------------------------
 *
 * pathnodes.h
 *	  规划者内部数据结构的定义，特别是路径。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/nodes/pathnodes.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef PATHNODES_H
#define PATHNODES_H

#include "access/sdir.h"
#include "lib/stringinfo.h"
#include "nodes/params.h"
#include "nodes/parsenodes.h"
#include "storage/block.h"


/*
 * Relids
 *		关系标识符集合（索引到范围表）。
 */
typedef Bitmapset *Relids;

/*
 * 在寻找“最便宜路径”时，此枚举指定我们希望
 * 最便宜的启动成本还是最便宜的总成本。
 */
typedef enum CostSelector
{
	STARTUP_COST, TOTAL_COST
} CostSelector;

/*
 * cost_qual_eval()产生的成本估计包括一次性
 * （启动）成本和每个元组的成本。
 */
typedef struct QualCost
{
	Cost		startup;		/* 一次性成本 */
	Cost		per_tuple;		/* 每次评估成本 */
} QualCost;

/*
 * 计算聚合函数执行的成本需要这些关于
 * 由给定Agg节点执行的聚合的统计信息。请注意，成本
 * 包括聚合参数表达式的执行成本以及聚合函数本身的成本。此外，字段必须
 * 被定义，以确保用memset将结构初始化为零是正确的。
 */
typedef struct AggClauseCosts
{
	QualCost	transCost;		/* 每输入行的总执行成本 */
	QualCost	finalCost;		/* 每聚合行的总成本 */
	Size		transitionSpace;	/* 用于按引用传递的过渡数据的空间 */
} AggClauseCosts;

/*
 * 此枚举标识我们在规划过程中可能处理的不同类型的“上层”（扫描后/连接）关系。
 */
typedef enum UpperRelationKind
{
	UPPERREL_SETOP,				/* UNION/INTERSECT/EXCEPT 的结果（如果有的话） */
	UPPERREL_PARTIAL_GROUP_AGG, /* 部分分组/聚合的结果（如果有的话） */
	UPPERREL_GROUP_AGG,			/* 分组/聚合的结果（如果有的话） */
	UPPERREL_WINDOW,			/* 窗口函数的结果（如果有的话） */
	UPPERREL_PARTIAL_DISTINCT,	/* 部分 "SELECT DISTINCT" 的结果（如果有的话） */
	UPPERREL_DISTINCT,			/* "SELECT DISTINCT" 的结果（如果有的话） */
	UPPERREL_ORDERED,			/* ORDER BY 的结果（如果有的话） */
	UPPERREL_FINAL				/* 任何剩余顶层操作的结果 */
	/* 注意：UPPERREL_FINAL 必须是最后一个枚举项；它用于调整数组大小 */
} UpperRelationKind;

/*----------
 * PlannerGlobal
 *		规划/优化的全局信息
 *
 * PlannerGlobal 为整个规划器调用保持状态；这个状态
 * 在被规划的命令中所有层次的子查询之间共享。
 *----------
 */
typedef struct PlannerGlobal
{
	NodeTag		type;

	ParamListInfo boundParams;	/* 提供给 planner() 的参数值 */

	List	   *subplans;		/* 子计划节点的计划 */

	List	   *subroots;		/* 子计划节点的 PlannerInfos */

	Bitmapset  *rewindPlanIDs;	/* 需要 REWIND 的子计划的索引 */

	List	   *finalrtable;	/* 执行器的 "扁平" rangetable */

	List	   *finalrowmarks;	/* PlanRowMarks 的 "扁平" 列表 */

	List	   *resultRelations;	/* 整数 RT 索引的 "扁平" 列表 */

	List	   *appendRelations;	/* AppendRelInfos 的 "扁平" 列表 */

	List	   *relationOids;	/* 计划依赖的关系 OID */

	List	   *invalItems;		/* 其他依赖项，作为 PlanInvalItems */

	List	   *paramExecTypes; /* PARAM_EXEC 参数的类型 OID */

	Index		lastPHId;		/* 分配的最高 PlaceHolderVar ID */

	Index		lastRowMarkId;	/* 分配的最高 PlanRowMark ID */

	int			lastPlanNodeId; /* 分配的最高计划节点 ID */

	bool		transientPlan;	/* 当 TransactionXmin 变化时，是否重做计划？ */

	bool		dependsOnRole;	/* 计划是否特定于当前角色？ */

	bool		parallelModeOK; /* 并行模式潜在可行？ */

	bool		parallelModeNeeded; /* 实际需要并行模式？ */

	char		maxParallelHazard;	/* 最差 PROPARALLEL 危险级别 */

	PartitionDirectory partition_directory; /* 分区描述符 */
} PlannerGlobal;

/* 用于获取与 SubPlan 节点相关的计划的宏 */
#define planner_subplan_get_plan(root, subplan) \
	((Plan *) list_nth((root)->glob->subplans, (subplan)->plan_id - 1))


/*----------
 * PlannerInfo
 *		用于规划/优化的每个查询信息
 *
 * 这个结构在所有计划器例程中通常被称为 "root"。
 * 它包含了计划器的所有工作状态的链接，除了原始的 Query。
 * 请注意，目前计划器会广泛修改传入的 Query 数据结构；总有一天这应该停止。
 *
 * 由于在 optimizer/optimizer.h 中解释的原因，我们在这里或在那个头文件中定义 typedef，
 * 以先读取的为准。
 *----------
 */
#ifndef HAVE_PLANNERINFO_TYPEDEF
typedef struct PlannerInfo PlannerInfo;
#define HAVE_PLANNERINFO_TYPEDEF 1
#endif

struct PlannerInfo
{
	NodeTag		type;

	Query	   *parse;			/* 正在规划的 Query */

	PlannerGlobal *glob;		/* 当前规划运行的全局信息 */

	Index		query_level;	/* 外层 Query 中的 1 */

	PlannerInfo *parent_root;	/* 外层 Query 中的 NULL */

	/*
	 * plan_params 包含了这一查询级别需要提供给
	 * 当前正在规划的较低查询级别的表达式。
	 * outer_params 包含了外层查询级别将提供给该查询级别的 PARAM_EXEC 参数的 paramIds。
	 */
	List	   *plan_params;	/* PlannerParamItems 的列表，见下文 */
	Bitmapset  *outer_params;

	/*
	 * simple_rel_array 保存指向 "基关系" 和 "其他关系" 的指针（详见 RelOptInfo 的注释以获取更多信息）。
	 * 它按 rangetable 索引进行索引（所以条目 0 始终被浪费）。
	 * 当一个 RTE 不对应于基关系时，条目可以为 NULL，例如连接 RTE 或未引用视图 RTE；
	 * 或者如果 RelOptInfo 尚未生成。
	 */
	struct RelOptInfo **simple_rel_array;	/* 所有 1-rel RelOptInfos */
	int			simple_rel_array_size;	/* 数组的分配大小 */

	/*
	 * simple_rte_array 的长度与 simple_rel_array 相同，保存指向相关 rangetable 条目的指针。
	 * 使用这个比使用 rt_fetch() 快一些，这主要是由于较少的间接访问。
	 */
	RangeTblEntry **simple_rte_array;	/* 将 rangetable 作为数组 */

	/*
	 * append_rel_array 的长度与上述数组相同，保存指向相应的 AppendRelInfo 条目的指针，按 child_relid 索引，
	 * 如果 rel 不是 appendrel 子项，则为 NULL。如果 append_rel_list 为空，则不会分配数组本身。
	 */
	struct AppendRelInfo **append_rel_array;

	/*
	 * all_baserels 是一个 Relids 集合，包含查询中的所有基 relids（而不是 "其他"
	 * relids）；也就是说，我们需要形成的最终连接的 Relids 标识符。
	 * 这在 make_one_rel 中计算，正好在我们开始创建 Paths 之前。
	 */
	Relids		all_baserels;

	/*
	 * nullable_baserels 是一个 Relids 集合，包含通过联合树中的某些外部连接可空的基 relids；
	 * 这些是在 WHERE 子句、SELECT 目标列表等下面潜在可空的 rels。
	 * 这在 deconstruct_jointree 中计算。
	 */
	Relids		nullable_baserels;

	/*
	 * join_rel_list 是我们在这次规划运行中考虑的所有连接关系 RelOptInfos 的列表。
	 * 对于小问题，我们只需扫描列表进行查找，但当有许多连接关系时，我们构建哈希表以加快查找速度。
	 * 当 join_rel_hash 非 NULL 时，哈希表存在且有效。
	 * 请注意，即使在使用哈希表进行查找时，我们仍然保持列表；
	 * 这简化了 GEQO 的生活。
	 */
	List	   *join_rel_list;	/* 连接关系 RelOptInfos 的列表 */
	struct HTAB *join_rel_hash; /* 可选的连接关系哈希表 */

	/*
	 * 在进行动态规划风格的连接搜索时，join_rel_level[k] 是所有级别为 k 的连接关系 RelOptInfos 的列表，
	 * join_cur_level 是当前级别。新的连接关系 RelOptInfos 会自动添加到 join_rel_level[join_cur_level] 列表中。
	 * 如果不使用，则 join_rel_level 为 NULL。
	 */
	List	  **join_rel_level; /* 连接关系 RelOptInfos 的列表 */
	int			join_cur_level; /* 正在扩展的列表索引 */

	List	   *init_plans;		/* 为查询初始化 SubPlans */

	List	   *cte_plan_ids;	/* 每个 CTE 项目的子计划 ID 列表（如果没有为该 CTE 制作子计划，则为 -1） */

	List	   *multiexpr_params;	/* MULTIEXPR 子查询输出的参数列表的列表 */

	List	   *eq_classes;		/* 活动 EquivalenceClasses 的列表 */

	bool		ec_merging_done;	/* 一旦 ECs 是规范的则设置为 true */

	List	   *canon_pathkeys; /* "规范" PathKeys 的列表 */

	List	   *left_join_clauses;	/* 用于可合并连接的外部连接子句的 RestrictInfos 列表，其中左侧有非空变量 */

	List	   *right_join_clauses; /* 可合并连接的外连接子句的 RestrictInfos 列表，
									 * 右侧为非空变量 */

	List	   *full_join_clauses;	/* mergejoinable的RestrictInfos列表
									 * 完全连接子句 */

	List	   *join_info_list; /* SpecialJoinInfos列表 */

	/*
	 * all_result_relids在SELECT时为空，其他情况下至少包含
	 * parse->resultRelation。对于跨继承
	 * 或分区树的UPDATE/DELETE/MERGE，结果关系的子关系id已被添加。
	 * 当使用多级分区时，中间的分区关系
	 * 也包含在内。leaf_result_relids类似，只不过它仅包括实际结果
	 * 表，而不是分区表。
	 */
	Relids		all_result_relids;	/* 所有结果关系id的集合 */
	Relids		leaf_result_relids; /* 所有叶子关系id的集合 */

	/*
	 * 注意：对于描述分区表分区的AppendRelInfos，
	 * 我们保证在分区表的PartitionDesc中较早出现的分区
	 * 会在append_rel_list中较早出现。
	 */
	List	   *append_rel_list;	/* AppendRelInfos 列表 */

	List	   *row_identity_vars;	/* RowIdentityVarInfos 的列表 */

	List	   *rowMarks;		/* PlanRowMarks 的列表 */

	List	   *placeholder_list;	/* PlaceHolderInfos 列表 */

	List	   *fkey_list;		/* ForeignKeyOptInfos列表 */

	List	   *query_pathkeys; /* query_planner()的期望路径键 */

	List	   *group_pathkeys; /* groupClause路径键（如果有） */
	List	   *window_pathkeys;	/* 底部窗口的路径键（如果有） */
	List	   *distinct_pathkeys;	/* distinctClause路径键（如果有） */
	List	   *sort_pathkeys;	/* sortClause路径键（如果有） */

	List	   *part_schemes;	/* 在查询中使用的规范化分区方案 */

	List	   *initial_rels;	/* 我们现在尝试连接的RelOptInfos */

	/* 使用fetch_upper_rel()获取任何特定的上层关系 */
	List	   *upper_rels[UPPERREL_FINAL + 1]; /* upper-rel RelOptInfos */

	/* 被grouping_planner选择的用于上层处理的结果tlists */
	struct PathTarget *upper_targets[UPPERREL_FINAL + 1];

	/*
	 * 完全处理的目标列表保存在这里。它与
	 * parse->targetList不同，因为（对于INSERT）它已被重新排序以匹配
	 * 目标表，并已填充默认值。此外，还可能存在其他
	 * resjunk目标。preprocess_targetlist()完成了大部分
	 * 工作，但请注意，在appendrel扩展期间可能会添加更多的resjunk目标。
	 * （因此，upper_targets在那之后不能被设置。）
	 */
	List	   *processed_tlist;

	/*
	 * 对于UPDATE，该列表包含目标表属性编号，
	 * 这些编号对应于processed_tlist的前N个条目。
	 * （processed_tlist中的任何其他条目必须是resjunk。）请勿使用
	 * processed_tlist中的resnos来识别UPDATE目标列。
	 */
	List	   *update_colnos;

	/* 在create_plan()期间填写的字段以供setrefs.c使用 */
	AttrNumber *grouping_map;	/* 用于GroupingFunc修复 */
	List	   *minmax_aggs;	/* MinMaxAggInfos列表 */

	MemoryContext planner_cxt;	/* 持有PlannerInfo的上下文 */

	Cardinality total_table_pages;	/* 查询中所有非虚拟表的页面数 */

	Selectivity tuple_fraction; /* 传递给query_planner的tuple_fraction */
	Cardinality limit_tuples;	/* 传递给query_planner的limit_tuples */

	Index		qual_security_level;	/* quals的最低security_level */
	/* 注意：如果没有securityQuals，qual_security_level为零 */

	bool		hasJoinRTEs;	/* 如果任何RTE是RTE_JOIN类型则为真 */
	bool		hasLateralRTEs; /* 如果任何RTE被标记为LATERAL则为真 */
	bool		hasHavingQual;	/* 如果havingQual非空则为真 */
	bool		hasPseudoConstantQuals; /* 如果任何RestrictInfo的pseudoconstant = true则为真 */
	bool		hasAlternativeSubPlans; /* 如果我们已做出任何这些则为真 */
	bool		hasRecursion;	/* 如果正在计划递归WITH项目则为真 */

	/*
	 * 有关聚合的信息。由preprocess_aggrefs()填充。
	 */
	List	   *agginfos;		/* AggInfo结构 */
	List	   *aggtransinfos;	/* AggTransInfo结构 */
	int			numOrderedAggs; /* 带DISTINCT/ORDER BY/WITHIN GROUP的数字 */
	bool		hasNonPartialAggs;	/* 是否有任何聚合不支持部分模式？ */
	bool		hasNonSerialAggs;	/* 是否有任何部分聚合不可序列化？ */

	/* 这些字段仅在hasRecursion为真时使用： */
	int			wt_param_id;	/* 工作表的PARAM_EXEC ID */
	struct Path *non_recursive_path;	/* 非递归项的路径 */

	/* 这些字段是createplan.c的工作空间 */
	Relids		curOuterRels;	/* 当前节点上方的外部关系 */
	List	   *curOuterParams; /* 尚未分配的NestLoopParams */

	/* 这些字段是setrefs.c的工作空间 */
	bool	   *isAltSubplan;	/* 与glob->subplans对应的数组 */
	bool	   *isUsedSubplan;	/* 与glob->subplans对应的数组 */

	/* join_search_hook的可选私有数据，例如，GEQO */
	void	   *join_search_private;

	/* 此查询是否修改任何分区键列？ */
	bool		partColsUpdated;
};


/*
 * 在已知simple_rte_array[]必须已准备好的地方， 
 * 我们只需索引它以获取RTE。 
 * 在可能在进入query_planner()之前或之后执行的代码中，使用此宏。
 */
#define planner_rt_fetch(rti, root) \
	((root)->simple_rte_array ? (root)->simple_rte_array[rti] : \
	 rt_fetch(rti, (root)->parse->rtable))

/*
 * 如果多个关系以相同方式进行分区，则所有这些分区
 * 将指向相同的PartitionScheme。一个PartitionScheme对象的列表
 * 附加到PlannerInfo。按设计，分区方案
 * 仅包含分区方法的一般属性（LIST与
 * RANGE、分区列数和每个列的类型信息）
 * 而不是具体的边界。
 *
 * 我们存储opclass声明的输入数据类型而不是分区键
 * 数据类型，因为前者而不是后者用于比较
 * 分区边界。由于分区键数据类型和opclass声明的
 * 输入数据类型预计是二进制兼容的（根据ResolveOpClass），
 * 这两者应该具有相同的byval和长度属性。
 */
typedef struct PartitionSchemeData
{
	char		strategy;		/* 分区策略 */
	int16		partnatts;		/* 分区属性的数量 */
	Oid		   *partopfamily;	/* 操作符族的OID */
	Oid		   *partopcintype;	/* opclass声明的输入数据类型的OID */
	Oid		   *partcollation;	/* 分区排序的OID */

	/* 关于分区键数据类型的缓存信息。 */
	int16	   *parttyplen;
	bool	   *parttypbyval;

	/* 关于分区比较函数的缓存信息。 */
	struct FmgrInfo *partsupfunc;
}			PartitionSchemeData;

typedef struct PartitionSchemeData *PartitionScheme;

/*----------
 * RelOptInfo
 *		每个关系的规划/优化信息
 *
 * 对于规划目的，"base rel" 是一个普通关系（一个表）或出现在范围表中的子SELECT或函数的输出。
 * 在这两种情况下，它通过RT索引唯一标识。"joinrel"是两个或多个基本关系的连接。
 * 一个joinrel由其组成基本关系的RT索引集识别。我们为每个baserel和joinrel创建RelOptInfo
 * 节点，并分别将它们存储在PlannerInfo的simple_rel_array和join_rel_list中。
 *
 * 请注意，对于任何给定的组成基本关系集合，只有一个joinrel，无论我们以何种顺序组装它；所以一个无序
 * 集合是用于识别它的正确数据类型。
 *
 * 我们还有"其他关系"，它们类似于基本关系，因为它们引用单个RT索引；但它们不是连接树的一部分，并被赋予
 * 不同的RelOptKind以识别它们。
 * 目前其他关系唯一的类型是为"附加关系"的成员关系制作的，即继承集或UNION ALL子查询。
 * 附加关系具有一个作为基本关系的父RTE，表示整个附加关系。成员RTE是其他关系。父关系在查询连接
 * 树中存在，但成员则不在。成员RTE和其他关系用于规划附加集的单个表或子查询的扫描；然后，父
 * baserel被赋予包含各个成员关系最佳路径的Append和/或MergeAppend路径。 （有关更多信息，请参见
 * AppendRelInfo的注释。）
 *
 * 曾几何时，我们也曾创建其他关系以表示连接RTE，以便处理连接别名变量。目前这不是必需的，因为
 * 所有连接别名变量在preprocess_expression期间被展开为非别名形式。
 *
 * 我们还有表示不同分区表的子关系之间连接的关系。这些关系不会被添加到
 * join_rel_level列表中，因为它们不是通过动态编程算法直接连接的。
 *
 * 还有一种RelOptKind用于"上层"关系，它们是描述扫描/连接后处理步骤的RelOptInfos，例如聚合。
 * 这些RelOptInfos中的许多字段没有意义，但它们的Path字段始终包含显示执行该处理步骤的路径。
 *
 * 最后，还有一种RelOptKind用于"死"关系，它们是我们已经证明不需要连接的基本关系。
 *
 * 这个数据结构的部分是特定于各种扫描和连接机制的。为了它们没有意义，所以没有创建新节点类型。
 *
 *		relids - 基本关系标识符集；如果只有一个，则是基本关系，如果有多个，则是连接关系
 *		rows - 在应用限制子句后估算的关系中元组的数量（即，它的计划输出行）
 *		consider_startup - 如果在启动成本低的基础上保持普通路径对该rel有任何价值，则为真
 *		consider_param_startup - 对于参数化路径也是如此
 *		reltarget - 此关系列的默认Path输出tlist；通常包含我们需要输出的
 *					Var和PlaceHolderVar节点。
 *					列表没有特定顺序，但附加关系集中的所有关系必须使用相应的顺序。
 *					注意：在附加关系子关系中，可能包含从子查询中提取的任意表达式！
 *		pathlist - Path节点的列表，每个节点表示生成关系的潜在有用方法
 *		ppilist - 参数化路径的ParamPathInfo节点，如果有的话
 *		cheapest_startup_path - 在未参数化路径中启动成本最低的pathlist成员；
 *			或如果没有未参数化路径，则为NULL
 *		cheapest_total_path - 未参数化路径中总成本最低的pathlist成员；
 *			或如果没有未参数化路径，则为在最小参数化路径中总成本最低的路径
 *		cheapest_unique_path - 用于缓存产生唯一（无重复）输出的最便宜路径；
 *			如果尚未请求，则为NULL
 *		cheapest_parameterized_paths - 其参数化的最佳路径；
 *			始终包括cheapest_total_path，即使这是未参数化的
 *		direct_lateral_relids - 该关系直接LATERAL引用的关系
 *		lateral_relids - LATERAL的所需外部关系，作为Relids集
 *			（包括直接和间接的lateral引用）
 *
 * 如果该关系是基本关系，则会设置以下字段：
 *
 *		relid - RTE索引（这与relids字段是冗余的，但为方便访问而提供）
 *		rtekind - RTE的rtekind字段的副本
 *		min_attr, max_attr - 关系的有效AttrNumbers范围
 *		attr_needed - 位图集数组，指示每个属性所需的最高连接关系；
 *				如果位0被设置，则该属性在最终targetlist中是所需的
 *		attr_widths - 每个属性宽度估计的缓存空间；
 *					  为零表示尚未计算
 *		lateral_vars - 该关系的横向交叉引用，如果有的话（Vars和PlaceHolderVars的列表）
 *		lateral_referencers - 以横向方式引用此关系的关系的relids
 *				（包括直接和间接横向引用）
 *		indexlist - 该关系索引的IndexOptInfo节点的列表
 *					（如果不是表，始终为NIL）
 *		pages - 关系中的磁盘页面数（如果不是表，则为零）
 *		tuples - 关系中的元组数（不考虑限制）
 *		allvisfrac - 标记为全可见的磁盘页面的比例
 *		eclass_indexes - 提到此关系的EquivalenceClasses（仅在EC合并完成后填充）
 *		subroot - 子查询的PlannerInfo（如果不是子查询，则为NULL）
 *		subplan_params - 要传递给子查询的PlannerParamItems列表
 *
 *		注意：对于子查询，元组和subroot不会在创建RelOptInfo对象时立即设置；
 *		它们会在set_subquery_pathlist处理对象时填充。
 *
 *		对于作为附加关系成员的其他关系，这些字段将会被填写，
 *		就像baserel一样，只是我们不在乎lateral_vars。
 *
 * 如果该关系是外部表或连接同属于同一外部服务器且分配给相同用户
 * 以检查访问权限的外部表的连接（参见checkAsUser），这些字段将被设置：
 *
 *		serverid - 外部服务器的OID，如果是外部表（否则为InvalidOid）
 *		userid - 用于检查访问的用户OID（InvalidOid表示当前用户）
 *		useridiscurrent - 我们假定userid等于当前用户
 *		fdwroutine - FDW的函数钩子，如果是外部表（否则为NULL）
 *		fdw_private - 外部表的FDW私有状态（否则为NULL）
 *
 * 有两个字段用于缓存在连接搜索中获取的知识，以确定此关系在与给定其他
 * 关系连接时是否可以证明是唯一的，即，在连接关系中匹配给定行的行数最多只有一行。
 * 目前我们仅尝试这种证明，因此仅填充这些字段，用于基本关系；但是将来它们可能也会用于连接关系：
 *
 *		unique_for_rels - Relid集合列表，每个集合都是证明此关系唯一的其他
 *					关系的集合
 *		non_unique_for_rels - Relid集合列表，每个集合都是我们尝试且未能证明
 *					此关系唯一的其他关系的集合
 *
 * 以下字段的存在取决于关系参与的限制和连接：
 *
 *		baserestrictinfo - RestrictInfo节点的列表，包含关于
 *					该关系参与的每个非连接资格子句的信息（仅用于基本关系）
 *		baserestrictcost - 在单个元组上评估baserestrictinfo
 *					子句的估计成本（仅用于基本关系）
 *		baserestrict_min_security - 在baserestrictinfo中的
 *					子句中找到的最小security_level
 *		joininfo  - RestrictInfo节点的列表，包含该关系参与的
 *					每个连接子句的信息（但请注意，这排除了可能从
 *					EquivalenceClasses导出的子句）
 *		has_eclass_joins - 表示EquivalenceClass连接可能的标志
 *
 * 注意：在RelOptInfo中保留restrictinfo列表对
 * 基本关系是有用的，因为对于连接关系，被视为限制子句的子句集取决于我们选择连接的子关系。
 * （例如，在一个3基关系连接中，涉及关系1和2的子句必须在连接{1}和{2 3}时视为限制子句；
 * 但如果我们连接{1 2}和{3}，那么该子句将在{1 2}中成为限制子句，并且在{1 2 3}的层级上不应该再次处理。）
 * 因此，在连接情况下，restrictinfo列表出现在单个JoinPaths（字段joinrestrictinfo）中，而不是在父关系中。
 * 但是RelOptInfo存储连接信息列表是可以的，因为对于给定的关系无论我们如何形成它都是相同的。
 *
 * 我们在RelOptInfo中存储baserestrictcost（对于基本关系），因为我们知道我们至少会需要它一次（来定价顺序扫描），
 * 并且可能需要多次来定价索引扫描。
 *
 * 如果关系是连接关系，且由两个分区的关系组成，具有匹配的分区方案，并依据分区列的等值连接进行连接,
 * 则该连接关系被认为是分区的。在这种情况下，我们可以考虑连接关系通过任一关系的分区键进行分区，
 * 虽然如果任一关系可以通过外连接强制为null，则需要小心。例如，外连接如（A LEFT JOIN B ON A.a = B.b）可能
 * 会生成B.b为NULL的行。这些行可能不符合对B施加的分区条件。因此，严格来说，该连接不通过B.b进行分区，
 * 因此外连接的分区键应仅包括来自不可为空的一侧的分区键表达式。然而，如果后续连接使用严格比较运算符
 * （而且所有常用的等值连接运算符都是严格的），则空值的存在不会造成问题：这样的行无法匹配其他侧的任何内容，因此
 * 它们不会产生做任何分区间子连接的需要。因此，我们可以将这些值视为仍然对连接输出进行分区，以便进行额外的
 * 分区方式连接，只要下一个连接使用严格连接运算符。
 *
 * 如果关系被分区，则这些字段将被设置：
 *
 *		part_scheme - 关系的分区方案
 *		nparts - 分区的数量
 *		boundinfo - 分区边界
 *		partbounds_merged - 如果分区边界已合并，则为真
 *		partition_qual - 如果不是根，则为分区约束
 *		part_rels - 每个分区的RelOptInfos
 *		all_partrels - 所有分区relids的Relids集
 *		partexprs, nullable_partexprs - 分区键表达式
 *
 * partexprs和nullable_partexprs数组各包含part_scheme->partnatts个元素。
 * 每个元素都是分区键表达式的列表。对于分区的基本关系，每个partexprs元素中的一个表达式，并且nullable_partexprs为空。
 * 对于分区的连接关系，连接中的每个基本关系都为每个分区列贡献一个分区键表达式；
 * 该表达式放入partexprs[i]列表中，如果基本关系在此连接或任何下级外连接中不可为空，
 * 或放入nullable_partexprs[i]列表中，如果基本关系是可为空的。
 * 此外，FULL JOIN会添加额外的nullable_partexprs表达式，
 * 对应于左侧和右侧连接列的COALESCE表达式，
 * 以简化将连接子句匹配到这些列表。
 *----------
 */ 

/* 表AM支持的标志位掩码 */
#define AMFLAG_HAS_TID_RANGE (1 << 0)

typedef enum RelOptKind
{
	RELOPT_BASEREL,
	RELOPT_JOINREL,
	RELOPT_OTHER_MEMBER_REL,
	RELOPT_OTHER_JOINREL,
	RELOPT_UPPER_REL,
	RELOPT_OTHER_UPPER_REL,
	RELOPT_DEADREL
} RelOptKind;

/*
 * 给定关系是否为简单关系，即基础或“其他”成员关系?
 */
#define IS_SIMPLE_REL(rel) \
	((rel)->reloptkind == RELOPT_BASEREL || \
	 (rel)->reloptkind == RELOPT_OTHER_MEMBER_REL)

/* 给定关系是否为连接关系? */
#define IS_JOIN_REL(rel)	\
	((rel)->reloptkind == RELOPT_JOINREL || \
	 (rel)->reloptkind == RELOPT_OTHER_JOINREL)

/* 给定关系是否为上级关系? */
#define IS_UPPER_REL(rel)	\
	((rel)->reloptkind == RELOPT_UPPER_REL || \
	 (rel)->reloptkind == RELOPT_OTHER_UPPER_REL)

/* 给定关系是否为“其他”关系? */
#define IS_OTHER_REL(rel) \
	((rel)->reloptkind == RELOPT_OTHER_MEMBER_REL || \
	 (rel)->reloptkind == RELOPT_OTHER_JOINREL || \
	 (rel)->reloptkind == RELOPT_OTHER_UPPER_REL)

typedef struct RelOptInfo
{
	NodeTag		type;

	RelOptKind	reloptkind;

	/* 包含在此RelOptInfo中的所有关系 */
	Relids		relids;			/* 基础relids的集合（范围表索引） */

	/* 规划器生成的大小估计 */
	Cardinality rows;			/* 估计的结果元组数量 */

	/* 每个关系的规划器控制标志 */
	bool		consider_startup;	/* 保持低启动成本路径吗? */
	bool		consider_param_startup; /* 同上，针对参数化路径? */
	bool		consider_parallel;	/* 考虑并行路径吗? */

	/* 扫描此关系的路径的默认结果目标列表 */
	struct PathTarget *reltarget;	/* Vars/Exprs 列表，成本，宽度 */

	/* 材料化信息 */
	List	   *pathlist;		/* 路径结构 */
	List	   *ppilist;		/* 用于路径列表的ParamPathInfos */
	List	   *partial_pathlist;	/* 部分路径 */
	struct Path *cheapest_startup_path;
	struct Path *cheapest_total_path;
	struct Path *cheapest_unique_path;
	List	   *cheapest_parameterized_paths;

	/* 基础关系和连接关系所需的参数化信息 */
	/* （另见lateral_vars和lateral_referencers） */
	Relids		direct_lateral_relids;	/* 直接被侧面引用的关系 */
	Relids		lateral_relids; /* 关系的最小参数化 */

	/* 有关基础关系的信息（连接关系不设置！） */
	Index		relid;
	Oid			reltablespace;	/* 包含的表空间 */
	RTEKind		rtekind;		/* RELATION，SUBQUERY，FUNCTION等 */
	AttrNumber	min_attr;		/* 关系的最小属性编号（通常<0） */
	AttrNumber	max_attr;		/* rel的最大attrno */
	Relids	   *attr_needed;	/* 数组索引 [min_attr .. max_attr] */
	int32	   *attr_widths;	/* 数组索引 [min_attr .. max_attr] */
	List	   *lateral_vars;	/* 被关系引用的LATERAL Vars和PHVs */
	Relids		lateral_referencers;	/* 侧面引用我的关系 */ 
	List	   *indexlist;		/* IndexOptInfo 的列表 */
	List	   *statlist;		/* StatisticExtInfo 的列表 */
	BlockNumber pages;			/* 从 pg_class 派生的大小估计 */
	Cardinality tuples;
	double		allvisfrac;
	Bitmapset  *eclass_indexes; /* 在 PlannerInfo 的 eq_classes 列表中提到此关系的索引 */
	PlannerInfo *subroot;		/* 如果是子查询 */
	List	   *subplan_params; /* 如果是子查询 */
	int			rel_parallel_workers;	/* 所需的并行工作者数量 */
	uint32		amflags;		/* 表 AM 支持的可选功能的位掩码 */

	/* 有关外部表和外连接的信息 */
	Oid			serverid;		/* 为表或连接识别服务器 */
	Oid			userid;			/* 识别用于检查访问的用户 */
	bool		useridiscurrent;	/* 连接仅对当前用户有效 */
	/* 使用 "struct FdwRoutine" 以避免在此处包含 fdwapi.h */
	struct FdwRoutine *fdwroutine;
	void	   *fdw_private;

	/* 用于记忆我们是否已证明此关系唯一的缓存空间 */
	List	   *unique_for_rels;	/* 对于这些其他 relid 的已知唯一集 */
	List	   *non_unique_for_rels;	/* 对于这些集合的已知非唯一 */

	/* 由各种扫描和连接使用： */
	List	   *baserestrictinfo;	/* RestrictInfo 结构（如果是基础关系） */
	QualCost	baserestrictcost;	/* 评估上述内容的成本 */
	Index		baserestrict_min_security;	/* 在 baserestrictinfo 中发现的最小 security_level */
	List	   *joininfo;		/* 涉及此关系的连接子句的 RestrictInfo 结构 */
	bool		has_eclass_joins;	/* T 表示 joininfo 不完整 */

	/* 由分区连接使用： */
	bool		consider_partitionwise_join;	/* 是否考虑分区连接路径？（如果是分区关系） */
	Relids		top_parent_relids;	/* 顶层父项的 Relids（如果是“其他”关系） */

	/* 用于分区关系： */
	PartitionScheme part_scheme;	/* 分区方案 */
	int			nparts;			/* 分区数量；如果尚未设置则为 -1；在连接关系的情况下，0 表示被认为是非分区的 */
	struct PartitionBoundInfoData *boundinfo;	/* 分区边界 */
	bool		partbounds_merged;	/* 如果分区边界是由 partition_bounds_merge() 创建则为真 */
	List	   *partition_qual; /* 分区约束，如果不是根 */
	struct RelOptInfo **part_rels;	/* 分区的 RelOptInfos 数组，
									 * 以与边界相同的顺序存储 */
	Bitmapset  *live_parts;		/* 位图，成员作为 part_rels[] 数组的索引
								 * 指示哪些分区经过分区裁剪幸存。 */
	Relids		all_partrels;	/* 所有分区 relids 的 Relids 集合 */
	List	  **partexprs;		/* 非空的分区键表达式 */
	List	  **nullable_partexprs; /* 可为空的分区键表达式 */
} RelOptInfo;

/*
 * 给定关系是否是分区的？
 *
 * 仅仅测试 rel->part_scheme 是否被设置是不够的，因为输入关系的基本分区属性可能匹配
 * 但分区边界可能不匹配。此外，如果我们能够证明 rel 是虚拟的（为空），则此后应将其视为未分区。
 */
#define IS_PARTITIONED_REL(rel) \
	((rel)->part_scheme && (rel)->boundinfo && (rel)->nparts > 0 && \
	 (rel)->part_rels && !IS_DUMMY_REL(rel))

/*
 * 便利宏以确保分区关系具有所有
 * 所需的成员设置。
 */
#define REL_HAS_ALL_PART_PROPS(rel)	\
	((rel)->part_scheme && (rel)->boundinfo && (rel)->nparts > 0 && \
	 (rel)->part_rels && (rel)->partexprs && (rel)->nullable_partexprs)

/*
 * IndexOptInfo
 *		每个索引的规划/优化信息
 *
 *		indexkeys[]，indexcollations[] 各有 ncolumns 条目。
 *		opfamily[] 和 opcintype[] 各有 nkeycolumns 条目。它们不
 *		包含关于包含属性的任何信息。
 *
 *		sortopfamily[]，reverse_sort[] 和 nulls_first[] 有
 *		nkeycolumns 条目，如果索引是有序的；但如果是无序的，
 *		那些指针为 NULL。
 *
 *		indexkeys[] 数组中的零表示索引列是
 *		表达式；indexprs 中对应每个这样的列有一个元素。
 *
 *		对于有序索引，reverse_sort[] 和 nulls_first[] 描述
 *		顺序索引扫描的排序顺序；我们也可以考虑反向
 *		索引扫描，这将产生反向排序。
 *
 *		indexprs 和 indpred 表达式已经通过
 *		prepqual.c 和 eval_const_expressions() 处理，以便于匹配
 *		WHERE 子句。indpred 是隐式-AND 形式。
 *
 *		indextlist 是一个目标条目列表，表示索引列。
 *		它为每个简单列提供一个等效的基础关系 Var，
 *		并链接到每个表达式列的匹配 indexprs 元素。
 *
 *		虽然这些字段中的大多数在创建 IndexOptInfo 时被填充
 *		（通过 plancat.c），但 indrestrictinfo 和 predOK 在
 *		check_index_predicates() 中稍后设置。
 */
#ifndef HAVE_INDEXOPTINFO_TYPEDEF
typedef struct IndexOptInfo IndexOptInfo;
#define HAVE_INDEXOPTINFO_TYPEDEF 1
#endif

struct IndexOptInfo
{
	NodeTag		type;

	Oid			indexoid;		/* 索引关系的 OID */
	Oid			reltablespace;	/* 索引的表空间（不是表） */
	RelOptInfo *rel;			/* 指向索引表的反向链接 */

	/* 索引大小统计信息（来自 pg_class 和其他地方） */
	BlockNumber pages;			/* 索引中的磁盘页面数量 */
	Cardinality tuples;			/* 索引中的元组数量 */
	int			tree_height;	/* 索引树的高度，或未知时为 -1 */

	/* 索引描述符信息 */
	int			ncolumns;		/* 索引中的列数 */
	int			nkeycolumns;	/* 索引中的键列数 */
	int		   *indexkeys;		/* 索引属性的列号，包括
								 * 键和包含的列，或 0 */
	Oid		   *indexcollations;	/* 索引列的排序规则 OIDs */
	Oid		   *opfamily;		/* 列的运算符家族 OIDs */
	Oid		   *opcintype;		/* opclass声明的输入数据类型的OID */
	Oid		   *sortopfamily;	/* btree 运算符家族的 OIDs，如果可排序 */
	bool	   *reverse_sort;	/* 排序顺序是否为降序？ */
	bool	   *nulls_first;	/* NULL 是否在排序顺序中首先出现？ */
	bytea	  **opclassoptions; /* 针对列的特定于 opclass 的选项 */
	bool	   *canreturn;		/* 哪些索引列可以在
								 * 索引唯一扫描中返回？ */
	Oid			relam;			/* 访问方法的 OID（在 pg_am 中） */

	List	   *indexprs;		/* 非简单索引列的表达式 */
	List	   *indpred;		/* 如果是部分索引，则为谓词，否则为 NIL */

	List	   *indextlist;		/* 表示索引列的目标列表 */

	List	   *indrestrictinfo;	/* 父关系的 baserestrictinfo 列表，减去索引谓词所隐含的任何条件（除非这是目标关系，参见 check_index_predicates() 中的注释） */

	bool		predOK;			/* 如果索引谓词与查询匹配则为 true */
	bool		unique;			/* 如果是唯一索引则为 true */
	bool		immediate;		/* 唯一性是否立即得到保证？ */
	bool		hypothetical;	/* 如果索引实际上并不存在则为 true */

	/* 剩余字段从索引 AM 的 API 结构复制： */
	bool		amcanorderbyop; /* AM 是否支持按运算符结果排序？ */
	bool		amoptionalkey;	/* 查询是否可以省略第一列的键？ */
	bool		amsearcharray;	/* AM 能处理 ScalarArrayOpExpr 限制吗？ */
	bool		amsearchnulls;	/* AM 能搜索 NULL/NOT NULL 条目吗？ */
	bool		amhasgettuple;	/* AM 是否具有 amgettuple 接口？ */
	bool		amhasgetbitmap; /* AM 是否具有 amgetbitmap 接口？ */
	bool		amcanparallel;	/* AM 是否支持并行扫描？ */
	bool		amcanmarkpos;	/* AM 是否支持标记/恢复？ */
	/* 与其在此包含 amapi.h，我们这样声明 amcostestimate */
	void		(*amcostestimate) ();	/* AM 的成本估算器 */
};

/*
 * ForeignKeyOptInfo
 *		用于规划/优化的每个外键信息
 *
 * 每个外键列的数组可以是固定大小的，因为我们允许在外键约束中最多有
 * INDEX_MAX_KEYS 列。每个数组都有 nkeys 个有效条目。
 */
typedef struct ForeignKeyOptInfo
{
	NodeTag		type;

	/* 有关外键的基本数据（从目录中获取）： */
	Index		con_relid;		/* 引用表的 RT 索引 */
	Index		ref_relid;		/* 被引用表的 RT 索引 */
	int			nkeys;			/* 外键中的列数 */
	AttrNumber	conkey[INDEX_MAX_KEYS]; /* 引用表中的列 */
	AttrNumber	confkey[INDEX_MAX_KEYS];	/* 被引用表中的列 */
	Oid			conpfeqop[INDEX_MAX_KEYS];	/* PK = FK 操作符 OID */

	/* 关于 FK 的相等条件是否匹配查询的派生信息： */
	int			nmatched_ec;	/* 与 EC 匹配的 FK 列数 */
	int			nconst_ec;		/* 这些 EC 中 ec_has_const 的数量 */
	int			nmatched_rcols; /* 与非 EC rinfos 匹配的 FK 列数 */
	int			nmatched_ri;	/* 与 FK 匹配的非 EC rinfos 的总数 */
	/* 指向与每列条件匹配的 eclass 的指针（如果有的话） */
	struct EquivalenceClass *eclass[INDEX_MAX_KEYS];
	/* 指向引用 Var 的 eclass 成员的指针（如果有的话） */
	struct EquivalenceMember *fk_eclass_member[INDEX_MAX_KEYS];
	/* 匹配每列条件的非 EC RestrictInfos 列表 */
	List	   *rinfos[INDEX_MAX_KEYS];
} ForeignKeyOptInfo;

/*
 * StatisticExtInfo
 *		关于规划/优化的扩展统计信息
 *
 * 每个 pg_statistic_ext 行由一个或多个此类型的节点表示，甚至在
 * 如果没有通过 ANALYZE 来计算它们，则表示为零。
 */
typedef struct StatisticExtInfo
{
	NodeTag		type;

	Oid			statOid;		/* 统计行的 OID */
	bool		inherit;		/* 包括子关系 */
	RelOptInfo *rel;			/* 统计信息表的反向链接 */
	char		kind;			/* 此条目的统计类型 */
	Bitmapset  *keys;			/* 涉及的列的 attnums */
	List	   *exprs;			/* 表达式 */
} StatisticExtInfo;

/*
 * 等价类
 *
 * 每当我们可以确定一个可合并 join 的等式子句 A = B
 * 没有被外连接延迟时，我们会创建一个包含表达式 A 和 B 的等价类，
 * 以记录这一知识。如果我们后来发现另一个等价关系 B = C，
 * 我们将 C 添加到现有的等价类中；这可能需要将两个现有的
 * 等价类合并。在条件分布过程结束时，我们得到了已知所有传递
 * 相等的值的集合，其中“相等”是根据 ec_opfamilies 中所示的 btree
 * 操作符族的规则，以及 ec_collation 所示的排序方式。(我们
 * 将一个 EC 限制为仅包含属于相同操作符族集合的相等关系。这
 * 可能可以放宽，但现在不值得麻烦，因为几乎所有的相等操作符
 * 都只属于一个 btree 操作类。同样，我们假设所有或没有输入
 * 数据类型是可排序的，因此单个排序值是足够的。)
 *
 * 我们还将等价类用作 PathKeys 的基础结构，让我们能够表示
 * 不同排序顺序等价的知识。由于每个 PathKey 必须引用一个
 * 等价类，当排序键表达式没有等同于其他任何东西时，我们将
 * 以单成员等价类结束。这样的等价类也可能包含一个不稳定的
 * 表达式（“ORDER BY random()”），这是一个在其他情况下无法
 * 出现的案例，因为包含不稳定函数的子句从不被认为是可合并
 * 的。我们特别标记这样的等价类，以防止它们与普通等价类合并。
 * 此外，对于不稳定的表达式，我们必须小心匹配等价类到正确的
 * 目标列表条目：考虑 SELECT random() AS a, random() AS b ... 
 * ORDER BY b,a。因此我们记录原始排序子句的 SortGroupRef。
 *
 * 我们允许出现在外连接的可为空一侧的相等子句形成等价类，
 * 但这些具有略微不同的含义：包含的值可能全为 NULL 而不是
 * 所有相同的非空值。有关这一点的更多信息，请参见 src/backend/optimizer/README。
 *
 * 注意：如果 ec_merged 不是 NULL，则该类已被合并到另一个类中，
 * 应忽略，以便使用指向的类。
 */
typedef struct EquivalenceClass
{
	NodeTag		type;

	List	   *ec_opfamilies;	/* btree 操作符族 OID */
	Oid			ec_collation;	/* 排序方式，如果数据类型是可排序的 */
	List	   *ec_members;		/* 等价成员列表 */
	List	   *ec_sources;		/* 生成 RestrictInfos 的列表 */
	List	   *ec_derives;		/* 派生 RestrictInfos 的列表 */
	Relids		ec_relids;		/* ec_members 中出现的所有 relids，除了
								 * 子成员（见下文） */
	bool		ec_has_const;	/* ec_members 中有任何伪常量吗？ */
	bool		ec_has_volatile;	/* （唯一）成员是一个不稳定表达式 */
	bool		ec_below_outer_join;	/* 等价关系适用于外连接下方 */
	bool		ec_broken;		/* 生成所需子句失败？ */
	Index		ec_sortref;		/* 原始排序子句标签，或 0 */
	Index		ec_min_security;	/* ec_sources 中的最低安全级别 */
	Index		ec_max_security;	/* ec_sources 中的最高 security_level */
	struct EquivalenceClass *ec_merged; /* 如果合并到另一个 EC，设置此项 */
} EquivalenceClass;

/*
 * 如果一个 EC 包含一个常量且不在外连接下方，
 * 依赖于它的任何 PathKey 必须是冗余的，因为
 * 关键字只有一个可能的值。
 */
#define EC_MUST_BE_REDUNDANT(eclass)  \
	((eclass)->ec_has_const && !(eclass)->ec_below_outer_join)


/*
 * EquivalenceMember - 等价类的一个成员表达式
 *
 * em_is_child 表示该元素是通过转置一个成员为附加关系父关系构建的 
 * 以表示对应的附加关系子关系的表达式。这些成员用于确定 
 * 对子关系的扫描路径键，并在必要时显式排序子关系，以为整个附加关系 
 * 树构建一个 MergeAppend 路径。一个 em_is_child 成员对 EC 的整体特性没有影响； 
 * 特别是，EC 的 ec_relids 字段不包括子关系。em_is_child 成员永远不应标记为 em_is_const 
 * 或导致 ec_has_const 或 ec_has_volatile 被设置。因此，em_is_child 成员并不是真正的 EC 
 * 完整成员，而只是实际成员的反映或翻版。对等价类的大多数操作 
 * 应忽略 em_is_child 成员，而那些不忽略的操作应测试 em_relids 
 * 以确保仅考虑相关成员。
 *
 * em_datatype 通常与 exprType(em_expr) 相同，但在处理二进制兼容的 opfamily 时可以不同； 
 * 特别是任何数组操作都绝不会在没有这个的情况下工作。在查找特定的 btree 操作符 
 * 以处理此表达式时使用 em_datatype。
 */
typedef struct EquivalenceMember
{
	NodeTag		type;

	Expr	   *em_expr;		/* 表示的表达式 */
	Relids		em_relids;		/* em_expr 中出现的所有 relids */
	Relids		em_nullable_relids; /* 由下外连接可为 null */
	bool		em_is_const;	/* 表达式是伪常量？ */
	bool		em_is_child;	/* 为子关系的派生版本？ */
	Oid			em_datatype;	/* opfamily 使用的“名称类型” */
} EquivalenceMember;

/*
 * PathKeys
 *
 * 路径的排序顺序由一系列 PathKey 节点表示。 
 * 空列表表示没有已知的排序。否则，第一项表示主要排序键，第二项表示第一个次要排序键， 
 * 等等。被排序的值通过链接到包含该值的 EquivalenceClass 并在其 
 * ec_opfamilies 中包括 pk_opfamily 来表示。EquivalenceClass 还指示使用哪个排序规则。 
 * 这是一种方便的方法，因为它使检测等价和密切相关的排序变得简单。（有关更多信息，请参阅 optimizer/README。）
 *
 * 注意：pk_strategy 是 BTLessStrategyNumber（对于 ASC）或 
 * BTGreaterStrategyNumber（对于 DESC）。我们假设所有支持排序的 
 * 索引类型将使用 btree 兼容策略号。
 */
typedef struct PathKey
{
	NodeTag		type;

	EquivalenceClass *pk_eclass;	/* 订购的值 */
	Oid			pk_opfamily;	/* btree 操作族定义排序 */
	int			pk_strategy;	/* 排序方向 (ASC 或 DESC) */
	bool		pk_nulls_first; /* NULL 值是否在正常值之前？ */
} PathKey;

/*
 * VolatileFunctionStatus -- 允许节点缓存其
 * contain_volatile_functions 属性。VOLATILITY_UNKNOWN 意味着尚未
 * 确定。
 */
typedef enum VolatileFunctionStatus
{
	VOLATILITY_UNKNOWN = 0,
	VOLATILITY_VOLATILE,
	VOLATILITY_NOVOLATILE
} VolatileFunctionStatus;

/*
 * PathTarget
 *
 * 这个结构包含在规划过程中关于 Path 将要计算的
 * targetlist（输出列）所需的信息。每个 RelOptInfo
 * 包含一个默认的 PathTarget，其各个 Paths 可以简单地
 * 引用。然而，在某些情况下，一个 Path 可能计算的输出
 * 与其他 Paths 不同，在这种情况下，我们为其制作一个自定义
 * PathTarget。举例来说，一个 indexscan 可能返回索引表达式，
 * 否则需要显式计算。(请注意，“上层”关系通常没有有用的默认
 * PathTargets。)
 *
 * exprs 包含裸表达式；它们没有 TargetEntry 节点，
 * 尽管这些在完成的 Plans 中会出现。
 *
 * sortgrouprefs[] 是与 exprs 相同长度的数组，包含相应的
 * sort/group 引用编号，对于未被 sort/group 子句引用的
 * 表达式则为零。如果 sortgrouprefs 为 NULL（通常在
 * RelOptInfo.reltarget 目标中是这样的；只有上层 Paths 包含
 * 此信息），则我们尚未在此 tlist 中识别出 sort/group 列。
 * 这使我们在需要时可以以较少的开销处理 sort/group
 * 引用编号，而不必在 exprs 列表中包括 TargetEntry 节点。
 */
typedef struct PathTarget
{
	NodeTag		type;
	List	   *exprs;			/* 要计算的表达式列表 */
	Index	   *sortgrouprefs;	/* 相应的 sort/group 引用编号，或 0 */
	QualCost	cost;			/* 评估表达式的成本 */
	int			width;			/* 结果元组的估计平均宽度 */
	VolatileFunctionStatus has_volatile_expr;	/* 指示 exprs 是否包含
												 * 任何波动函数。 */
} PathTarget;

/* 获取 PathTarget 中的 sort/group 引用编号的方便宏 */
#define get_pathtarget_sortgroupref(target, colno) \
	((target)->sortgrouprefs ? (target)->sortgrouprefs[colno] : (Index) 0)


/*
 * ParamPathInfo
 *
 * 针对给定关系及给定所需外部关系的所有参数化路径
 * 链接到单个 ParamPathInfo，该信息存储此参数化的
 * 估计行数等公共信息。我们这样做部分是为了避免重
 * 复计算，但主要是为了确保每条此类路径的估计行数
 * 实际上是相同的。
 *
 * 注意：ppi_clauses 仅在基础关系路径的 ParamPathInfos 中使用；
 * 在连接的情况下它是 NIL，因为相关子句的集合根据
 * 连接的形式而变化。相关子句将出现在每个参数化
 * 连接路径的 joinrestrictinfo 列表中。
 */
typedef struct ParamPathInfo
{
	NodeTag		type;

	Relids		ppi_req_outer;	/* 提供路径使用的参数的关系 */
	Cardinality ppi_rows;		/* 估计的结果元组数量 */
	List	   *ppi_clauses;	/* 可从外部关系获取的连接子句 */
} ParamPathInfo;


/*
 * 类型“Path”按原样用于顺序扫描路径，以及一些
 * 其他简单计划类型，这些类型不需要任何额外的路径
 * 信息。对于其他路径类型，它是一个较大结构的第一
 * 个组件。
 *
 * “pathtype”是我们可以从此路径构建的计划节点的
 * NodeTag。它与路径的 NodeTag 部分冗余，但允许我们在
 * 路径处理过程中使用相同的路径类型来区分多个计划类型，
 * 当无需区分计划类型时。
 *
 * “parent”标识此路径扫描的关系，“pathtarget”
 * 描述此路径将计算的准确输出列集合。在简单情况下，
 * 给定关系的所有 Paths 共享相同的 targetlist，
 * 我们通过使 path->pathtarget 等于 parent->reltarget 来
 * 表示这一点。
 *
 * “param_info”，如果不为 NULL，链接到一个 ParamPathInfo，
 * 该信息标识提供参数值给此路径每次扫描的外部
 * 关系。这意味着此路径只能通过使用此路径在内部的
 * 嵌套循环连接与这些关系连接。还请注意，参数化路径
 * 负责测试涉及此关系和指定外部关系的所有“可移动”
 * 连接子句。
 *
 * “rows”在简单路径中与 parent->rows 相同，但在参数化
 * 路径和 UniquePaths 中，它可以小于 parent->rows，反映出
 * 我们通过额外的连接条件进行了筛选或去除了重复项的事实。
 *
 * “pathkeys”是一个 PathKey 节点的列表（见上文），
 * 描述路径输出行的排序顺序。
 */
typedef struct Path
{
	NodeTag		type;

	NodeTag		pathtype;		/* 标识扫描/连接方法的标签 */

	RelOptInfo *parent;			/* 此路径可以构建的关系 */
	PathTarget *pathtarget;		/* Vars/Exprs 列表，成本，宽度 */

	ParamPathInfo *param_info;	/* 参数化信息，如果没有则为 NULL */

	bool		parallel_aware; /* 启动并行感知逻辑？ */
	bool		parallel_safe;	/* 可作为并行计划的一部分使用吗？ */
	int			parallel_workers;	/* 所需工作线程数量；0 = 不并行 */

	/* 估计路径的大小/成本（有关更多信息，请参阅 costsize.c） */
	Cardinality rows;			/* 估计的结果元组数量 */
	Cost		startup_cost;	/* 获取任何元组之前的成本 */
	Cost		total_cost;		/* 总成本（假设获取所有元组） */

	List	   *pathkeys;		/* 路径输出的排序顺序 */
	/* pathkeys 是一个 PathKey 节点的列表；见上文 */
} Path;

/* 提取路径参数化 relids 的宏；注意双重评估 */
#define PATH_REQ_OUTER(path)  \
	((path)->param_info ? (path)->param_info->ppi_req_outer : (Relids) NULL)

/*----------
 * IndexPath 代表对单个索引的索引扫描。
 *
 * 这个结构用于常规的索引扫描和仅索引扫描；
 * path.pathtype 是 T_IndexScan 或 T_IndexOnlyScan，以显示哪个是指。
 *
 * 'indexinfo' 是要扫描的索引。
 *
 * 'indexclauses' 是一个 IndexClause 节点的列表，每个节点代表一个
 * 可索引的限制，列表之间具有隐含的 AND 语义。
 * 空列表表示全索引扫描。
 *
 * 'indexorderbys'，如果不是 NIL，是一个 ORDER BY 表达式的列表，这些表达式
 * 被发现可以用作 amcanorderbyop 索引的排序运算符。
 * 该列表必须与路径的 pathkeys 匹配，即，每个 pathkey 对应一个表达式，
 * 顺序相同。这些不是 RestrictInfos，仅是简单的表达式，
 * 因为它们通常不会产生布尔值。可以保证每个
 * 表达式在运算符的左侧有索引键。
 *
 * 'indexorderbycols' 是一个整数列表，包含索引列号（零基）
 * 的长度与 'indexorderbys' 相同，显示每个
 * ORDER BY 表达式要使用的索引列。（对于每个 ORDER BY 可以
 * 使用哪个索引列没有限制。）
 *
 * 'indexscandir' 是以下之一：
 *		ForwardScanDirection：有序索引的正向扫描
 *		BackwardScanDirection：有序索引的反向扫描
 *		NoMovementScanDirection：无序索引的扫描，或不关心
 * （执行器不关心它得到 ForwardScanDirection 还是
 * NoMovementScanDirection 进行索引扫描，但规划者希望
 * 区分有序和无序索引以构建 pathkeys。）
 *
 * 'indextotalcost' 和 'indexselectivity' 被保存在 IndexPath 中，以便
 * 当考虑在位图索引/堆扫描中使用相同的索引时，我们
 * 不需要重新计算它们（参见 BitmapHeapPath）。IndexPath
 * 自身的成本代表 IndexScan 或 IndexOnlyScan 计划类型的成本。
 *----------
 */
typedef struct IndexPath
{
	Path		path;
	IndexOptInfo *indexinfo;
	List	   *indexclauses;
	List	   *indexorderbys;
	List	   *indexorderbycols;
	ScanDirection indexscandir;
	Cost		indextotalcost;
	Selectivity indexselectivity;
} IndexPath;

/*
 * 每个 IndexClause 引用查询的 WHERE 或 JOIN 条件中的 RestrictInfo 节点，并显示如何将该限制应用于特定索引。我们支持两种类型的 indexclauses，一种是索引机械直接可用的，通常形式为 "indexcol OP pseudoconstant"，另一种是可以导出索引条件的。最简单的这种转换是形式为 "pseudoconstant OP indexcol" 的子句可以交换位置以产生一个可索引条件（索引机械总是期望 indexcol 在左侧）。另一个例子是我们可能能够从 LIKE 条件中提取一个可索引范围条件，比如 "x LIKE 'foo%bar'" 会产生 "x >= 'foo' AND x < 'fop'"。这种有损条件的导出由附加在 indexclause 顶层函数或操作符上的规划器支持函数完成。
 *
 * indexquals 是与此 IndexClause 相关的直接可用索引条件的 RestrictInfos 列表。在最简单的情况下，它是一个包含 iclause->rinfo 的单元素列表。否则，它包含一个或多个从给定子句中提取的直接可用索引条件。'lossy' 标志表示索引条件与原始子句在语义上是否等同，或者代表一个较弱的条件。
 *
 * 通常，indexcol 是该子句所操作的单个索引列的索引，indexcols 为 NIL。但是如果子句是 RowCompareExpr，indexcol 是主导列的索引，indexcols 是所有受影响列的列表。（注意，indexcols 与 indexquals 中实际可索引的 RowCompareExpr 的列匹配，可能与 rinfo 中的原始列不同。）
 *
 * IndexPath 的 IndexClause 列要求按索引列排序，即 indexcol 值必须形成一个非递减序列。（相同索引列的多个子句的顺序未指定。）
 */
typedef struct IndexClause
{
	NodeTag		type;
	struct RestrictInfo *rinfo; /* 原始限制或连接子句 */
	List	   *indexquals;		/* 从中导出的 indexqual(s) */
	bool		lossy;			/* indexquals 是 clause 的有损版本吗？ */
	AttrNumber	indexcol;		/* 子句使用的索引列（零基） */
	List	   *indexcols;		/* 多个索引列，如果 RowCompare */
} IndexClause;

/*
 * BitmapHeapPath 代表一个或多个生成 TID 位图的索引扫描，而不是直接访问堆，随后通过 AND/OR 组合产生单个位图，最后进行使用该位图的堆扫描。请注意，输出总是被认为是无序的，因为它将按物理堆顺序输出，无论底层索引如何。
 *
 * 各个索引扫描由 IndexPath 节点表示，任何在其上方的逻辑由 BitmapAndPath 和 BitmapOrPath 节点的树表示。请注意，我们可以使用相同的 IndexPath 节点来表示常规（或仅索引）索引扫描计划，作为 BitmapHeapPath 的子节点，代表使用 BitmapIndexScan 扫描相同的索引。IndexPath 的 startup_cost 和 total_cost 数值始终代表其作为常规（或仅索引）IndexScan 使用的成本。BitmapIndexScan 的成本可以使用 IndexPath 的 indextotalcost 和 indexselectivity 计算。
 */
typedef struct BitmapHeapPath
{
	Path		path;
	Path	   *bitmapqual;		/* IndexPath, BitmapAndPath, BitmapOrPath */
} BitmapHeapPath;

/*
 * BitmapAndPath 代表一个 BitmapAnd 计划节点；它只能作为 BitmapHeapPath 的子结构的一部分出现。Path 结构比我们实际需要的要重一点，但出于简单考虑，我们把它设为 Path 的派生。
 */
typedef struct BitmapAndPath
{
	Path		path;
	List	   *bitmapquals;	/* IndexPaths 和 BitmapOrPaths */
	Selectivity bitmapselectivity;
} BitmapAndPath;

/*
 * BitmapOrPath 代表一个 BitmapOr 计划节点；它只能作为 BitmapHeapPath 的子结构的一部分出现。Path 结构比我们实际需要的要重一点，但出于简单考虑，我们把它设为 Path 的派生。
 */
typedef struct BitmapOrPath
{
	Path		path;
	List	   *bitmapquals;	/* IndexPaths 和 BitmapAndPaths */
	Selectivity bitmapselectivity;
} BitmapOrPath;

/*
 * TidPath 代表通过 TID 的扫描
 *
 * tidquals 是一个隐式 OR 的查询表达式列表，形式为 "CTID = pseudoconstant"，或 "CTID = ANY(pseudoconstant_array)"，或与关系相关的 CurrentOfExpr。
 */
typedef struct TidPath
{
	Path		path;
	List	   *tidquals;		/* 涉及 CTID = 某个东西的 qual(s) */
} TidPath;

/*
 * TidRangePath 代表通过连续的 TID 范围的扫描
 *
 * tidrangequals 是一个隐式 AND 的查询表达式列表，形式为 "CTID relop pseudoconstant"，其中 relop 是 >,>=,<,<= 之一。
 */
typedef struct TidRangePath
{
	Path		path;
	List	   *tidrangequals;
} TidRangePath;

/*
 * SubqueryScanPath 代表对未展开的子查询进行扫描
 *
 * 请注意，subpath 来自不同的规划域；例如，其中的 RTE 索引的含义与 SubqueryScanPath 所知道的含义不同。path.parent->subroot 是解释 subpath 所需的规划上下文。
 */
typedef struct SubqueryScanPath
{
	Path		path;
	Path	   *subpath;		/* 代表子查询执行的路径 */
} SubqueryScanPath;


/*
 * ForeignPath表示潜在的外部表扫描、外部连接
 * 或外部上层关系。
 *
 * fdw_private存储关于扫描的FDW私有数据。虽然fdw_private在
 * 正常操作期间实际上并不会被核心代码触及，但通常使用可以通过
 * nodeToString()转储的表示是个好主意，以便在调试时使用
 * pprint()等工具检查结构。
 */
typedef struct ForeignPath
{
	Path		path;
	Path	   *fdw_outerpath;
	List	   *fdw_private;
} ForeignPath;

/*
 * CustomPath表示由某些外部扩展执行的表扫描或表连接。
 *
 * 我们在这里提供一组钩子——提供者必须小心地正确设置——以允许
 * 扩展提供自己的关系扫描或连接关系的方法。例如，提供者可以
 * 提供GPU加速、基于缓存的扫描或其他我们尚未想到的逻辑。
 *
 * CustomPaths可以通过set_rel_pathlist_hook或set_join_pathlist_hook
 * 函数分别注入到基础或连接关系的规划过程中。
 *
 * 核心代码必须避免假设CustomPath的大小仅如此处声明的结构；
 * 提供者被允许将其作为更大结构中的第一个元素。(由于规划器从不
 * 复制Paths，这并不会增加任何复杂性。)然而，为了与FDW情况的一致性，
 * 我们在CustomPath中提供了一个"custom_private"字段；提供者
 * 可能更倾向于使用该字段，而不是定义另一种结构类型。
 */

struct CustomPathMethods;

typedef struct CustomPath
{
	Path		path;
	uint32		flags;			/* CUSTOMPATH_* 标志的掩码，见
								 * nodes/extensible.h */
	List	   *custom_paths;	/* 子路径节点列表（如果有） */
	List	   *custom_private;
	const struct CustomPathMethods *methods;
} CustomPath;

/*
 * AppendPath表示一个追加计划，即，
 * 若干成员计划的连续执行。
 *
 * 对于部分追加，'subpaths'包含非部分子路径，后跟部分子路径。
 *
 * 注意：“subpaths”可能仅包含一个，甚至没有，
 * 元素。这些情况在create_append_plan过程中进行优化。
 * 特别地，没有子路径的AppendPath是一个“虚拟”的路径，
 * 用于表示一个关系是可证明为空的情况。
 * （这是一种方便的表示，因为这意味着当我们构建
 * 一个appendrel并发现所有子元素都已被排除时，
 * 不需要额外操作来识别该关系为虚拟。）
 */
typedef struct AppendPath
{
	Path		path;
	List	   *subpaths;		/* 组件路径的列表 */
	/* 在subpaths中第一个部分路径的索引；如果没有则为list_length(subpaths) */
	int			first_partial_path;
	Cardinality limit_tuples;	/* 输出元组的硬限制，或-1 */
} AppendPath;

#define IS_DUMMY_APPEND(p) \
	(IsA((p), AppendPath) && ((AppendPath *) (p))->subpaths == NIL)

/*
 * 已证明为空的关系将有一条虚拟路径
 * （但可能在其上有投影路径）。出于历史原因，
 * 这是作为一个宏提供的，封装了is_dummy_rel()。
 */
#define IS_DUMMY_REL(r) is_dummy_rel(r)
extern bool is_dummy_rel(RelOptInfo *rel);

/*
 * MergeAppendPath表示一个MergeAppend计划，
 * 即，从多个成员计划中合并排序结果以生成排序输出。
 */
typedef struct MergeAppendPath
{
	Path		path;
	List	   *subpaths;		/* 组件路径的列表 */
	Cardinality limit_tuples;	/* 输出元组的硬限制，或-1 */
} MergeAppendPath;

/*
 * GroupResultPath表示使用结果计划节点计算
 * 单纯GROUP BY情况的输出，其中我们知道应该生成
 * 正好一行，然后可能会通过HAVING条件过滤。
 *
 * 注意，quals是一个裸条款列表，而不是RestrictInfos。
 */
typedef struct GroupResultPath
{
	Path		path;
	List	   *quals;
} GroupResultPath;

/*
 * MaterialPath表示使用材料化计划节点，即，缓存
 * 其子路径的输出。当子路径开销大，需要重复扫描，
 * 或者当我们需要标记/恢复能力而子路径没有时，会使用它。
 */
typedef struct MaterialPath
{
	Path		path;
	Path	   *subpath;
} MaterialPath;

/*
 * MemoizePath表示一个记忆计划节点，即，一个缓存
 * 从参数化路径中缓存元组，以避免底层节点必须
 * 为已缓存的参数值重新扫描。
 */
typedef struct MemoizePath
{
	Path		path;
	Path	   *subpath;		/* 从中缓存元组的外部路径 */
	List	   *hash_operators; /* 哈希等价操作的OID作为缓存键 */
	List	   *param_exprs;	/* 作为缓存键的表达式 */
	bool		singlerow;		/* 如果缓存项将在缓存第一个记录后标记为
								 * 完成则为真。 */
	bool		binary_mode;	/* 当缓存键应逐位比较时为真，使用哈希相等操作时为假 */
	Cardinality calls;			/* 预期的重扫描次数 */
	uint32		est_entries;	/* 规划者预计将适合缓存的最大条目数，或如果未知则为0 */
} MemoizePath;

/*
 * UniquePath表示从其子路径的输出中消除不同的行。
 *
 * 这可以表示显著不同的计划：基于哈希或基于排序的实现，或者如果输入路径已被证明是不同的，则为无操作。该决策是足够局部的，因此不值得拥有单独的路径节点类型。(注意：在无操作的情况下，我们可以完全消除UniquePath节点，只返回子路径；但在路径树中拥有UniquePath来信号上层例程输入已知为不同是方便的。)
 */
typedef enum UniquePathMethod
{
	UNIQUE_PATH_NOOP,			/* 输入已知为唯一 */
	UNIQUE_PATH_HASH,			/* 使用哈希 */
	UNIQUE_PATH_SORT			/* 使用排序 */
} UniquePathMethod;

typedef struct UniquePath
{
	Path		path;
	Path	   *subpath;
	UniquePathMethod umethod;
	List	   *in_operators;	/* IN子句的相等运算符 */
	List	   *uniq_exprs;		/* 需要唯一化的表达式 */
} UniquePath;

/*
 * GatherPath并行运行多个计划副本并收集结果。并行领导者也可以执行计划，除非设置了single_copy标志。
 */
typedef struct GatherPath
{
	Path		path;
	Path	   *subpath;		/* 每个工作线程的路径 */
	bool		single_copy;	/* 不要重复执行路径 */
	int			num_workers;	/* 寻求帮助的工作线程数量 */
} GatherPath;

/*
 * GatherMergePath并行运行多个计划副本并收集结果，保持它们的共同排序顺序。
 */
typedef struct GatherMergePath
{
	Path		path;
	Path	   *subpath;		/* 每个工作线程的路径 */
	int			num_workers;	/* 寻求帮助的工作线程数量 */
} GatherMergePath;


/*
 * 所有连接类型的路径共享这些字段。
 */

typedef struct JoinPath
{
	Path		path;

	JoinType	jointype;

	bool		inner_unique;	/* 每个外部元组可证明不匹配超过一个内部元组 */

	Path	   *outerjoinpath;	/* 连接外侧的路径 */
	Path	   *innerjoinpath;	/* 连接内侧的路径 */

	List	   *joinrestrictinfo;	/* 应用于连接的RestrictInfos */

	/*
	 * 请参阅RelOptInfo和ParamPathInfo的注释，以了解为何
	 * joinrestrictinfo在JoinPath中是必要的，并且不能合并到
	 * 父RelOptInfo中。
	 */
} JoinPath;

/*
 * 嵌套循环路径不需要特殊字段。
 */

typedef struct NestPath
{
	JoinPath	jpath;
} NestPath;

/*
 * 合并连接路径具有这些字段。
 *
 * 与其他路径类型不同，MergePath节点不代表单个运行时计划节点：它最多可以代表四个。除了MergeJoin节点本身外，还可以为外部输入提供一个Sort节点、为内部输入提供一个Sort节点和/或为内部输入提供一个Material节点。我们可以通过单独的路径节点来表示这些节点，但考虑到在复杂连接问题中调查了多少不同的合并路径，似乎最好避免不必要的palloc开销。
 *
 * path_mergeclauses列出了将在合并中使用的子句（以RestrictInfos的形式）。
 *
 * 请注意，mergeclauses是父关系的限制子句列表的一个子集。任何不能合并连接的连接子句仅出现在父级的限制列表中，必须在执行时通过qpqual进行检查。
 *
 * outersortkeys（或内部排序键）如果外部路径（或内部路径）已经按正确顺序排序，则为NIL。如果不是NIL，则它是描述必须由显式Sort节点创建的顺序的PathKeys列表。
 *
 * skip_mark_restore为true表示执行器无需进行标记/恢复调用。标记/恢复开销通常是必需的，但如果我们知道执行器只需每个外部元组找到一个匹配项，并且mergeclauses足以识别匹配项，则可以跳过。在这种情况下，执行器在处理匹配后的外部关系可以立即推进，因此它永远不需要回退内部关系。
 *
 * materialize_inner为true表示应在内部输入上放置一个Material节点。这可能出现或不出现内部Sort步骤。
 */

typedef struct MergePath
{
	JoinPath	jpath;
	List	   *path_mergeclauses;	/* 用于合并的连接子句 */
	List	   *outersortkeys;	/* 显式排序的键，如果有的话 */
	List	   *innersortkeys;	/* 显式排序的键，如果有的话 */
	bool		skip_mark_restore;	/* 执行器可以跳过标记/恢复吗？ */
	bool		materialize_inner;	/* 添加 Materialize 到内部？ */
} MergePath;

/*
 * 哈希连接路径具有这些字段。
 *
 * 上述关于 mergeclauses 的备注同样适用于 hashclauses。
 *
 * 哈希连接不关心输入的顺序，因此我们不需要排序键。
 */

typedef struct HashPath
{
	JoinPath	jpath;
	List	   *path_hashclauses;	/* 用于哈希的连接子句 */
	int			num_batches;	/* 预计的批次数 */
	Cardinality inner_rows_total;	/* 预计的总内部行数 */
} HashPath;

/*
 * ProjectionPath 表示一个投影（即，目标列表计算）
 *
 * 名义上，该路径节点表示使用结果计划节点执行一个
 * 投影步骤。然而，如果输入计划节点支持投影，
 * 我们可以直接修改其输出目标列表以进行所需的计算，
 * 而不需要一个结果。在计划的某些地方，我们可以直接
 * 将期望的 PathTarget 嵌入到输入路径节点（并据此调整其成本），
 * 所以我们不需要 ProjectionPath。但在其他地方
 * 不修改输入路径节点是必要的，因此我们需要一个单独的
 * ProjectionPath 节点，该节点被标记为假，以表示我们打算将工作分配给输入计划节点。
 * ProjectionPath 节点的估计成本将考虑是否会使用结果。
 */
typedef struct ProjectionPath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
	bool		dummypp;		/* 如果不需要单独的结果，则为真 */
} ProjectionPath;

/*
 * ProjectSetPath 表示包含
 * 集合返回函数的目标列表的评估，该函数将由一个
 * ProjectSet 计划节点实现。
 */
typedef struct ProjectSetPath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
} ProjectSetPath;

/*
 * SortPath 表示显式排序步骤
 *
 * 排序键定义上与 path.pathkeys 相同。
 *
 * 注意：Sort 计划节点无法执行投影，因此 path.pathtarget 必须与
 * 输入的 pathtarget 相同。
 */
typedef struct SortPath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
} SortPath;

/*
 * IncrementalSortPath 表示增量排序步骤
 *
 * 这类似于常规排序，但假设某些前导键列已经按顺序排列。
 */
typedef struct IncrementalSortPath
{
	SortPath	spath;
	int			nPresortedCols; /* 预排序列的数量 */
} IncrementalSortPath;

/*
 * GroupPath 表示分组（预排序输入）
 *
 * groupClause 表示要进行分组的列；输入路径
 * 必须至少按此排序。
 *
 * 我们还可以对分组行应用 qual（相当于 HAVING）
 */
typedef struct GroupPath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
	List	   *groupClause;	/* SortGroupClause 的列表 */
	List	   *qual;			/* quals （HAVING quals），如果有的话 */
} GroupPath;

/*
 * UpperUniquePath 表示相邻重复项的去除（在预排序输入中）
 *
 * 要比较的列是路径的 pathkeys 的前 numkeys 列。
 * 输入假定已经按这种方式排序。
 */
typedef struct UpperUniquePath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
	int			numkeys;		/* 要比较的 pathkey 列的数量 */
} UpperUniquePath;

/*
 * AggPath 表示聚合函数的通用计算
 *
 * 这可能涉及简单分组（但不包括分组集），使用排序或哈希分组；对于 AGG_SORTED 情况，输入必须适当地预排序。
 */
typedef struct AggPath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
	AggStrategy aggstrategy;	/* 基本策略，见 nodes.h */
	AggSplit	aggsplit;		/* agg-拆分模式，见 nodes.h */
	Cardinality numGroups;		/* 输入中估计的组数 */
	uint64		transitionSpace;	/* 通过引用传递的过渡数据 */
	List	   *groupClause;	/* SortGroupClause 的列表 */
	List	   *qual;			/* quals （HAVING quals），如果有的话 */
} AggPath;

/*
 * 规划者中用于分组集的各种注释。
 */

typedef struct GroupingSetData
{
	NodeTag		type;
	List	   *set;			/* 分组集作为sortgrouprefs的列表 */
	Cardinality numGroups;		/* 估计结果组的数量 */
} GroupingSetData;

typedef struct RollupData
{
	NodeTag		type;
	List	   *groupClause;	/* 可用的解析->groupClause的子集 */
	List	   *gsets;			/* groupClause中整数索引的列表 */
	List	   *gsets_data;		/* GroupingSetData的列表 */
	Cardinality numGroups;		/* 估计结果组的数量 */
	bool		hashable;		/* 可以被哈希化 */
	bool		is_hashed;		/* 将实现为hashagg */
} RollupData;

/*
 * GroupingSetsPath表示一个GROUPING SETS聚合
 */

typedef struct GroupingSetsPath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
	AggStrategy aggstrategy;	/* 基本策略 */
	List	   *rollups;		/* RollupData 列表 */
	List	   *qual;			/* quals （HAVING quals），如果有的话 */
	uint64		transitionSpace;	/* 通过引用传递的过渡数据 */
} GroupingSetsPath;

/*
 * MinMaxAggPath 表示从索引计算 MIN/MAX 聚合
 */
typedef struct MinMaxAggPath
{
	Path		path;
	List	   *mmaggregates;	/* MinMaxAggInfo 列表 */
	List	   *quals;			/* HAVING quals，如果有的话 */
} MinMaxAggPath;

/*
 * WindowAggPath 表示窗口函数的通用计算
 */
typedef struct WindowAggPath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
	WindowClause *winclause;	/* 我们将使用的 WindowClause */
	List	   *qual;			/* 低级 WindowAgg 运行条件 */
	bool		topwindow;		/* 除了离计划根部最近的 WindowAgg 外，其他全部为 false */
} WindowAggPath;

/*
 * SetOpPath 表示一个集合操作，即 INTERSECT 或 EXCEPT
 */
typedef struct SetOpPath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
	SetOpCmd	cmd;			/* 应该做什么，参见 nodes.h */
	SetOpStrategy strategy;		/* 如何做到这一点，参见 nodes.h */
	List	   *distinctList;	/* SortGroupClauses 确定目标列 */
	AttrNumber	flagColIdx;		/* 标志列的位置，如果有的话 */
	int			firstFlag;		/* 第一个输入关系的标志值 */
	Cardinality numGroups;		/* 输入中估计的组数 */
} SetOpPath;

/*
 * RecursiveUnionPath 表示一个递归的 UNION 节点
 */
typedef struct RecursiveUnionPath
{
	Path		path;
	Path	   *leftpath;		/* 表示输入源的路径 */
	Path	   *rightpath;
	List	   *distinctList;	/* SortGroupClauses 确定目标列 */
	int			wtParam;		/* 表示工作表的 Param ID */
	Cardinality numGroups;		/* 输入中估计的组数 */
} RecursiveUnionPath;

/*
 * LockRowsPath 表示为 SELECT FOR UPDATE/SHARE 获取行锁
 */
typedef struct LockRowsPath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
	List	   *rowMarks;		/* PlanRowMark 的列表 */
	int			epqParam;		/* 为 EvalPlanQual 重新评估的 Param ID */
} LockRowsPath;

/*
 * ModifyTablePath 表示执行 INSERT/UPDATE/DELETE/MERGE
 *
 * 我们以字面意义表示 ModifyTable 计划节点中的大多数内容，
 * 只是我们有一个子 Path 而不是 Plan。但是对 OnConflictExpr 的分析
 * 被推迟到 createplan.c，FDW 数据的收集也是如此。
 */
typedef struct ModifyTablePath
{
	Path		path;
	Path	   *subpath;		/* 生成源数据的路径 */
	CmdType		operation;		/* INSERT、UPDATE、DELETE 或 MERGE */
	bool		canSetTag;		/* 我们设置命令标签/es_processed 吗？ */
	Index		nominalRelation;	/* 用于 EXPLAIN 的父 RT 索引 */
	Index		rootRelation;	/* 根 RT 索引，如果分区/继承的话 */
	bool		partColsUpdated;	/* 层次结构中的某个部分键已更新？ */
	List	   *resultRelations;	/* RT 索引的整数列表 */
	List	   *updateColnosLists;	/* 针对每个目标表的 update_colnos 列表 */
	List	   *withCheckOptionLists;	/* 针对每个目标表的 WCO 列表 */
	List	   *returningLists; /* 针对每个目标表的 RETURNING tlists */
	List	   *rowMarks;		/* PlanRowMarks（仅限非锁定） */
	OnConflictExpr *onconflict; /* ON CONFLICT 子句，或 NULL */
	int			epqParam;		/* 为 EvalPlanQual 重新评估的 Param ID */
	List	   *mergeActionLists;	/* 针对每个目标表的 MERGE 动作列表 */
} ModifyTablePath;

/*
 * LimitPath 表示应用 LIMIT/OFFSET 限制
 */
typedef struct LimitPath
{
	Path		path;
	Path	   *subpath;		/* 表示输入来源的路径 */
	Node	   *limitOffset;	/* OFFSET 参数，或者如果没有则为 NULL */
	Node	   *limitCount;		/* COUNT 参数，或者如果没有则为 NULL */
	LimitOption limitOption;	/* FETCH FIRST with ties or exact number */
} LimitPath;



/*
 * 限制子句信息。
 *
 * 我们为每个限制条件的AND子句（WHERE或JOIN/ON子句）创建一个这样的结构。
 * 由于限制子句在逻辑上是AND连接的，我们可以使用它们中的任何一个或它们的任何子集来过滤元组，
 * 而不必评估其余的。RestrictInfo节点本身存储优化器在选择最佳查询计划时使用的数据。
 *
 * 如果限制子句引用了单个基础关系，它将出现在该基础关系的RelOptInfo的baserestrictinfo列表中。
 *
 * 如果限制子句引用了多个基础关系，它将出现在每个描述限制子集的RelOptInfo的joininfo列表中
 * 这些基础关系在子句中提到。joininfo列表用于通过选择可行的连接候选来驱动连接树的构建。
 * 但是，直到我们构建包含它引用的所有基础关系的连接关系，该子句实际上不能被应用。
 *
 * 当我们构建一个包含在多关系限制子句中引用的所有基础关系的连接关系时，
 * 如果左子路径和右子路径均不包含子句中引用的所有基础关系，我们将该子句放入连接关系的
 * joinrestrictinfo列表中。该子句将在该连接级别应用，并且不会进一步上升到连接树中。（注意：
 * “谓词迁移”代码曾打算根据评估成本将限制子句上下移动于计划树，但这段代码已经无效，并且在
 * 可预见的未来不太可能被恢复。）
 *
 * 注意，在存在多个关系的情况下，多个关系限制可能会根据我们使用的连接序列在连接树中达到不同的高度。
 * 因此，这些子句不能直接与连接RelOptInfo关联，而必须在每个连接路径基础上进行跟踪。
 *
 * 表示等价条件的RestrictInfos（即，可合并连接的等式且不是外连接延迟的）处理有些不同。
 * 最初我们将它们附加到从中派生的EquivalenceClasses。当我们构建扫描或连接路径时，
 * 我们查看所有的EquivalenceClasses并生成派生的RestrictInfos，表示需要检查的
 * 最小条件集合，以确保该特定扫描或连接的所有EquivalenceClass的成员在所有发出
 * 的行中实际上是相等的。
 *
 * 在处理外连接时，我们必须非常小心地向上和向下推送qual子句。外连接的JOIN/ON条件必须
 * 在该连接节点处准确评估，除非它们是“退化”的条件，仅引用外连接可为空的部分的变量。
 * 出现在外连接之上的WHERE或JOIN中的qual如果引用任何可为空的变量，不能被向下推至
 * 外连接下方。RestrictInfo节点包含一个标志，以指示一个qual是否被推送到比它在连接树中的
 * 原始语法位置更低的级别。如果外连接阻止我们将qual推送到其“自然”的语义级别（与
 * 该qual中使用的基础关系相关的级别），那么我们会将该qual标记为带有“required_relids”值，
 * 包括不仅是它实际使用的基础关系。通过假装该qual引用所有需要形成外连接的关系，
 * 我们防止其在外连接的joinrel下方被评估。当我们确实形成外连接的joinrel时，我们仍然
 * 需要区分那些实际上在该连接的JOIN/ON条件中的qual与那些在树中的其他地方出现并推送
 * 到连接关系中的那些，因为它们没有使用其他关系。就是这个is_pushed_down标志的用途；
 * 它告诉我们一个qual不是所列required_relids基础关系集合的外连接qual。最初来自WHERE
 * 或INNER JOIN条件的子句将*始终*将其is_pushed_down标志设置为true。如果我们决定
 * 它可以被推入外连接的可为空侧，则外连接子句也有可能被标记为is_pushed_down。
 * 在这种情况下，它作为普通过滤qual在被评估的地方使用。
 * （简而言之，is_pushed_down对非退化外连接条件仅为false。我们可能应该重命名以反映该含义？
 * 但也请参见下面的RINFO_IS_PUSHED_DOWN的评论。）
 *
 * RestrictInfo节点还包含一个outerjoin_delayed标志，如果子句的适用性必须由于它下面出现的
 * 任何外连接而延迟，则该标志为true（即，它必须推迟到高于实际引用的关系集合的某个
 * 连接级别）。
 *
 * 还有一个outer_relids字段，除外连接子句外为NULL；对于那些，它是子句的外连接的外侧的
 * relids集合。（这些是该子句在参数化扫描中不能应用的关系，因为将其推入连接的外侧会导致
 * 错误的结果。）
 *
 * 还有一个nullable_relids字段，是子句引用的集合，这些关系可以在子句下面的某个外连接强制为空。
 *
 * outerjoin_delayed = true与nullable_relids != NULL之间有细微的不同：
 * 一个子句可能引用一些可为空的关系，但不被标记为outerjoin_delayed，因为它还引用了
 * 外连接的所有其他关系。未标记为outerjoin_delayed的子句可以在其可计算的任何地方得到执行。
 *
 * 为了高效处理安全屏障条件，我们用一个security_level字段标记RestrictInfo节点，
 * 其中较高的值识别来自不太可信来源的子句。其精确语义是：先前的子句的security_level
 * 值较低的子句不能在另一个子句之前被评估，除非前一个子句是防泄漏的。与外连接
 * 子句一样，这会导致子句有时需要在连接树中比其内容所暗示的更高的级别进行评估；
 * 即使在单个计划节点中，这一规则也约束了子句的应用顺序。
 *
 * 通常，引用的子句可能是任意复杂的。我们能够处理的作为indexscan quals、mergejoin
 * 子句或hashjoin子句的子句类型是有限的（例如，不允许使用易失性函数）。每种路径的代码
 * 负责识别可以使用的限制子句并忽略其余的。未通过indexscan、mergejoin或hashjoin实现
 * 的子句将被放置在完成的Plan节点的计划qual或joinqual字段中，由于其没有使用其他关系，
 * 它们将由通用的qual表达式评估代码进行强制执行。（但在估计结果元组计数时，如果
 * 我们能够猜测它的选择性，我们仍然有权计算它们的选择性……）
 *
 * 当引用的子句是OR子句时，我们生成一个修改的副本，在其中在顶级OR/AND结构下插入
 * 额外的RestrictInfo节点。这是OR indexscan处理的便利：
 * 从顶层或OR子子句中提取的indexquals将具有相关的RestrictInfo节点。
 *
 * 如果子句看起来可能对合并或哈希连接子句有用，则can_join标志设置为true，
 * 即如果它是一个带有左侧和右侧引用的不重叠relids的二元opclause。
 * （但是，实际上并不检查操作符是否可以合并或哈希连接。）
 *
 * 如果子句不包含当前查询级别的任何变量和任何易失性函数，则pseudoconstant标志设置为true。
 * 这样的子句可以被提取并用作一个在门控结果节点中的一次性qual。我们将pseudoconstant条款
 * 保存在与其他RestrictInfos相同的列表中，以便常规的子句推送机制可以将它们分配到正确
 * 的连接级别，但是它们需要在成本和选择性估计方面特别处理。请注意，pseudoconstant
 * 子句绝不能是indexqual或merge或hash join子句，因此对规划器的许多部分没有兴趣。
 *
 * 当从EquivalenceClasses生成连接子句时，可能有几种同样有效的方法来强制连接等价，
 * 我们只需应用其中一种。我们通过设置parent_ec指向生成的EquivalenceClass来标记此类子句。
 * 在同一连接中具有相同parent_ec的多个子句是冗余的。
 */

typedef struct RestrictInfo
{
	NodeTag		type;

	Expr	   *clause;			
/* WHERE 或 JOIN 的表示子句 */

	bool		is_pushed_down; /* 如果子句被下推到较低级别则为真 */

	bool		outerjoin_delayed;	/* 如果因下外连接而延迟则为真 */

	bool		can_join;		/* 请参见上面的注释 */

	bool		pseudoconstant; /* 请参见上面的注释 */

	bool		leakproof;		/* 如果已知不包含泄漏的变量则为真 */

	VolatileFunctionStatus has_volatile;	/* 表示子句是否包含
											 * 任何易失性函数。 */

	Index		security_level; /* 请参见上面的注释 */

	/* 在子句中实际引用的 relids (varnos) 集合： */
	Relids		clause_relids;

	/* 评估子句所需的 relids 集合： */
	Relids		required_relids;

	/* 如果是外连接子句，则为外侧关系，否则为 NULL： */
	Relids		outer_relids;

	/* 在子句中因下外连接而可为 NULL 的 relids： */
	Relids		nullable_relids;

	/* 这些字段适用于任何二元操作子句： */
	Relids		left_relids;	/* 子句左侧的 relids */
	Relids		right_relids;	/* 子句右侧的 relids */

	/* 除非子句为 OR 子句，否则该字段为 NULL： */
	Expr	   *orclause;		/* 修改后的子句与 RestrictInfos */

	/* 除非子句可能是冗余的，否则该字段为 NULL： */
	EquivalenceClass *parent_ec;	/* 生成等价类 */

	/* 成本和选择性缓存空间 */
	QualCost	eval_cost;		/* 子句的评估成本；如果尚未设置，则为 -1 */
	Selectivity norm_selec;		/* 对于“正常”（JOIN_INNER）
								 * 语义的选择性；如果尚未设置，则为 -1；>1 表示一个
								 * 冗余子句 */
	Selectivity outer_selec;	/* 外连接语义的选择性；如果
								 * 尚未设置，则为 -1 */

	/* 如果子句可合并连接，则有效，否则为 NIL */
	List	   *mergeopfamilies;	/* 包含子句操作符的操作族 */

	/* 合并子句处理的缓存空间；如果尚未设置，则为 NULL */
	EquivalenceClass *left_ec;	/* 包含左边的等价类 */
	EquivalenceClass *right_ec; /* 包含右边的等价类 */
	EquivalenceMember *left_em; /* 左边的等价成员 */
	EquivalenceMember *right_em;	/* 右边的等价成员 */
	List	   *scansel_cache;	/* MergeScanSelCache 结构的列表 */

	/* 在考虑特定连接路径时使用的临时工作区 */
	bool		outer_is_left;	/* T = 左侧的外部变量，F = 右侧的外部变量 */

	/* 如果子句可哈希连接，则有效，否则为 InvalidOid： */
	Oid			hashjoinoperator;	/* copy of clause operator */

	/* hashclause处理的缓存空间；如果尚未设置，则为-1 */
	Selectivity left_bucketsize;	/* 左侧的平均桶大小 */
	Selectivity right_bucketsize;	/* 右侧的平均桶大小 */
	Selectivity left_mcvfreq;	/* 左侧最常见值的频率 */
	Selectivity right_mcvfreq;	/* 右侧最常见值的频率 */

	/* 用于memoize节点的hash相等运算符，其他为InvalidOid */
	Oid			left_hasheqoperator;
	Oid			right_hasheqoperator;
} RestrictInfo;

/*
 * 此宏体现了测试RestrictInfo是否被“下推”到给定外连接的正确方法，
 * 即应被视为该外连接的过滤条件而不是连接条件。如果is_pushed_down为真，
 * 当然是如此；但再单独检查这一点已经不够，因为当我们为下层外连接生成路径时，
 * 外连接条件会被下推到更低的外连接，而该路径由上层外连接的LHS参数化。
 * 我们可以通过注意其required_relids超出连接的作用域来检测这样的条件。
 */
#define RINFO_IS_PUSHED_DOWN(rinfo, joinrelids) \
	((rinfo)->is_pushed_down || \
	 !bms_is_subset((rinfo)->required_relids, joinrelids))

/*
 * 由于mergejoinscansel()是一个相对昂贵的函数，并且在规划一个大的连接树时
 * 会被多次调用，我们尽量缓存其结果。每个可合并连接的RestrictInfo都
 * 包含一个已考虑用于该连接的特定排序顺序的列表，以及相应的选择性。
 */
typedef struct MergeScanSelCache
{
	/* 排序详情（缓存查找键） */
	Oid			opfamily;		/* btree 操作族定义排序 */
	Oid			collation;		/* 排序的排序规则 */
	int			strategy;		/* 排序方向 (ASC 或 DESC) */
	bool		nulls_first;	/* NULL 值是否在正常值之前？ */
	/* Results */
	Selectivity leftstartsel;	/* 条件左侧的首次连接比例 */
	Selectivity leftendsel;		/* 条件左侧的最后连接比例 */
	Selectivity rightstartsel;	/* 条件右侧的首次连接比例 */
	Selectivity rightendsel;	/* 条件右侧的最后连接比例 */
} MergeScanSelCache;

/*
 * 用于在计划树的最上层下方评估表达式的占位节点。
 * 这在规划期间用于表示所包含的表达式。
 * 在规划过程结束时，它将被所包含的表达式或一个Var替换，后者指向
 * 所包含表达式的低层评估。通常评估发生在外连接下方，而
 * 外连接上方的Var引用可能因此产生NULL而不是表达式值。
 *
 * 尽管规划器将其视为表达式节点类型，但解析器或执行器并不识别
 * 它，因此我们在此声明，而不是在primnodes.h中。
 */

typedef struct PlaceHolderVar
{
	Expr		xpr;
	Expr	   *phexpr;			/* 被表示的表达式 */
	Relids		phrels;			/* 语法上在expr源内的基础relids */
	Index		phid;			/* PHV的ID（在计划执行期间唯一） */
	Index		phlevelsup;		/* 如果PHV属于外查询，则> 0 */
} PlaceHolderVar;


/*
 * “特殊连接”信息。
 *
 * 单边外连接部分限制连接顺序，但并不完全限制。我们将这些连接扁平化到规划器的顶层关系连接列表中，但记录每个外连接的信息在SpecialJoinInfo结构体中。这些结构体保存在PlannerInfo节点的join_info_list中。
 *
 * 类似地，通过扁平化IN（子选择）和EXISTS（子选择）子句创建的半连接和反连接在连接顺序上创建部分约束。这些也同样记录在SpecialJoinInfo结构体中。
 *
 * 即使对于FULL JOIN没有规划灵活性，我们也为其创建SpecialJoinInfos，因为这简化了make_join_rel()的API。
 *
 * min_lefthand和min_righthand是进行特殊连接时每侧必须可用的基本relid集合。lhs_strict为true时表示特殊连接的条件当LHS变量全部为NULL时不能成功（这意味着，即使外连接出现在其RHS中，外连接可以与上级外连接换位置）。然而，我们并不为FULL JOIN设置lhs_strict。
 *
 * min_lefthand或min_righthand为空集合是无效的；如果它们是空的，这将破坏强制连接顺序的逻辑。
 *
 * syn_lefthand和syn_righthand是语法上在此特殊连接之下的基本relid集合。（这些对于计算更高连接的min_lefthand和min_righthand是必要的。）
 *
 * 如果我们检测到一个推送下去的子句必须在此连接形成后进行评估（因为它引用了RHS），则delay_upper_joins设置为true。任何在其RHS中有这种子句和此连接的外连接都不能与此连接调换位置，因为那样将没有地方来检查推送下去的子句。（我们对于FULL JOIN也不跟踪这一点。）
 *
 * 对于半连接，我们还提取连接操作符及其RHS参数，并设置semi_operators、semi_rhs_exprs、semi_can_btree和semi_can_hash。这是为了可能使RHS唯一化，所以我们不去做，除非至少有一个semi_can_btree和semi_can_hash可以被设置为true。（你可能期望这些信息在连接规划期间被计算；但在参数化表扫描的规划期间拥有这些信息是有帮助的，所以我们将其存储在SpecialJoinInfo结构体中。）
 *
 * jointype永远不为JOIN_RIGHT；RIGHT JOIN通过切换输入变成LEFT JOIN来处理。因此，join_info_list成员中jointype的允许值仅为LEFT、FULL、SEMI或ANTI。
 *
 * 为了连接选择性估计的目的，我们为常规内部连接创建临时的SpecialJoinInfo结构体；因此，在这样的结构体中，即使在join_info_list中不允许，jointype == JOIN_INNER也是可能的。我们还为外连接创建临时SpecialJoinInfos，其中jointype == JOIN_INNER，因为出于成本估计的目的，有时要了解在普通内连接语义下的连接大小是有用的。请注意，lhs_strict、delay_upper_joins，以及当然semi_xxx字段在此类结构体中没有有意义的设置。
 */
#ifndef HAVE_SPECIALJOININFO_TYPEDEF
typedef struct SpecialJoinInfo SpecialJoinInfo;
#define HAVE_SPECIALJOININFO_TYPEDEF 1
#endif

struct SpecialJoinInfo
{
	NodeTag		type;
	Relids		min_lefthand;	/* 最小LHS中的基本relid */
	Relids		min_righthand;	/* 最小RHS中的基本relid */
	Relids		syn_lefthand;	/* 语法上在LHS中的基本relid */
	Relids		syn_righthand;	/* 语法上在RHS中的基本relid */
	JoinType	jointype;		/* 始终为INNER、LEFT、FULL、SEMI或ANTI */
	bool		lhs_strict;		/* joinclause对于某些LHS rel是严格的 */
	bool		delay_upper_joins;	/* 不能与上级RHS换位置 */
	/* 剩余字段仅为JOIN_SEMI的jointype设置： */
	bool		semi_can_btree; /* 如果半操作符都是btree则为真 */
	bool		semi_can_hash;	/* 如果半操作符都是hash则为真 */
	List	   *semi_operators; /* 等值连接操作符的OID */
	List	   *semi_rhs_exprs; /* 这些操作的右侧表达式 */
};

/*
 * 附加关系信息。
 *
 * 当我们将一个可继承表或UNION-ALL子查询扩展为一个
 * “附加关系”（本质上是子RTE的列表）时，我们为每个子RTE构建一个
 * AppendRelInfo。AppendRelInfos的列表指示在扩展父级时必须包括
 * 哪些子RTE，每个节点携带在父列与子列之间进行转换所需的信息。
 *
 * 这些结构保存在PlannerInfo节点的append_rel_list中，append_rel_array[]
 * 提供了一种方便的查找方法，以获取与特定子关系ID关联的结构
 * （不过只能有一个，父关系可能在append_rel_list中有多个条目）。
 *
 * 注意：在规划准备阶段完成后，任何给定的RTE都是一个
 * 附加父项，如果其“inh”标志被设置，则其append_rel_list中有条目。
 * 我们清除那些实际上没有继承子项的普通表的“inh”标志，并且（为了
 * 滥用该标志的原意）我们为那些实际上是可平展UNION ALL查询的
 * 子查询RTE设置“inh”。这使我们避免了对append_rel_list的无用搜索。
 *
 * 注意：数据结构假设附加关系成员是单个基础关系。这对于继承是可以的，
 * 但它阻止我们提取一个包含连接的UNION ALL成员子查询。
 * 尽管这可以通过更复杂的数据结构解决，但目前这样做没有多大意义，
 * 因为计划不会因此得到改进。
 */

typedef struct AppendRelInfo
{
	NodeTag		type;

	/*
	 * 这些字段唯一标识此附加关系。对于同一个
	 * parent_relid，实际上应该有多个AppendRelInfos，但每个child_relid
	 * 只能有一个，因为给定的RTE不能是多个附加父项的子项。
	 */
	Index		parent_relid;	/* 附加父关系的RT索引 */
	Index		child_relid;	/* 附加子关系的RT索引 */

	/*
	 * 对于一个继承附加关系，父项和子项都是常规
	 * 关系，我们在这里存储它们的行类型OID，以用于转换
	 * 整行变量。对于UNION-ALL附加关系，父项和子项都是
	 * 没有命名行类型的子查询，我们在这里存储InvalidOid。
	 */
	Oid			parent_reltype; /* 父项复合类型的OID */
	Oid			child_reltype;	/* 子项复合类型的OID */

	/*
	 * 此列表的第N个元素是表示子列的Var或表达式，
	 * 对应于父项的第N列。这用于将引用父关系的Vars转换为
	 * 对子项的引用。如果对应于父项的已删除列，则列表元素为NULL
	 * （这仅在继承情况下可能发生，而在UNION ALL中不会）。
	 * 对于继承情况，列表元素始终是简单的Vars，但在UNION ALL情况下
	 * 可以是任意表达式。
	 *
	 * 请注意，我们仅存储用户列的条目（attno > 0）。
	 * 整行Vars是特别处理的，系统列（attno < 0）不需要特殊
	 * 转换，因为它们的attnos在所有表中都是相同的。
	 *
	 * 注意：这些Vars的varlevelsup = 0。在复制到子查询时，
	 * 请确保根据需要进行调整。
	 */
	List	   *translated_vars;	/* 子项Vars中的表达式 */

	/*
	 * 此数组简化了反向方向的转换，从
	 * 子列号到父列号。[ccolno - 1]的条目是
	 * 子列ccolno的基于1的父列号，如果该
	 * 子列已删除或在父项中不存在，则为零。
	 */
	int			num_child_cols; /* 数组的长度 */
	AttrNumber *parent_colnos;	/* 父attnos数组，或零 */

	/*
	 * 对于继承，我们在这里存储父表的OID，或者对于
	 * UNION ALL则为InvalidOid。这仅在尝试引用已删除的父列时
	 * 有助于生成错误消息。
	 */
	Oid			parent_reloid;	/* 父关系的OID */
} AppendRelInfo;


/*
 * 关于 UPDATE/DELETE/MERGE 中行标识 "resjunk" 列的信息。
 *
 * 在分区的 UPDATE/DELETE/MERGE 中，子分区尽可能共享行标识列是很重要的，
 * 以免消耗太多目标列表列。我们使用这些结构来跟踪哪些标识列已被请求。
 * 在完成的计划中，这些将产生 ModifyTable 的子计划节点目标列表中的一个
 * resjunk 项。
 *
 * RowIdentityVarInfos 中存储的所有 Var 必须具有 varno ROWID_VAR，
 * 以便于检测重复请求。我们将在最终计划中用生成关系的 varno 替换它。
 *
 * 在这个列表之外，varno 为 ROWID_VAR 和 varattno 为 k 的 Var 是对
 * row_identity_vars 列表中第 k 个元素的引用（k 从 1 开始计数）。
 * 在创建条目时，我们将这样的引用添加到 root->processed_tlist 中，
 * 并从那里传播到计划树中。
 */
typedef struct RowIdentityVarInfo
{
	NodeTag		type;

	Var		   *rowidvar;		/* 待评估的 Var（但 varno=ROWID_VAR） */
	int32		rowidwidth;		/* 估计的平均宽度 */
	char	   *rowidname;		/* resjunk 列的名称 */
	Relids		rowidrels;		/* 使用此的目标关系的 RTE 索引 */
} RowIdentityVarInfo;

/*
 * 对每个在规划期间生成的不同占位符表达式，我们在 PlannerInfo
 * 节点的 placeholder_list 中存储一个 PlaceHolderInfo 节点。它存储
 * 在每个 PlaceHolderVar 的副本中不需要的信息。phid 字段识别哪个
 * PlaceHolderInfo 与每个 PlaceHolderVar 关联。请注意，phid 在整个
 * 规划运行过程中是唯一的，不仅仅是在查询级别内——这是为了
 * 在将子查询拉入其父级时无需重新分配 ID。
 *
 * 其思想是在（仅）ph_eval_at 连接级别评估表达式，然后允许它像
 * Var 一样向上冒泡，直到 ph_needed 连接级别。ph_needed 的定义与
 * 常规 Var 的 attr_needed 相同。
 *
 * PlaceHolderVar 的表达式可能包含对其语法范围外的变量的 LATERAL 引用。
 * 如果是这样，这些关系将 *不* 包含在 ph_eval_at 中，但会记录在 ph_lateral 中。
 *
 * 注意，当 ph_eval_at 是连接而不是单个基关系时，PlaceHolderInfo 可能
 * 会对连接顺序施加约束：ph_eval_at 连接必须在任何会使 PlaceHolderVar 为空的
 * 外连接下方形成。
 *
 * 我们仅在确定 PlaceHolderVar 实际在计划树中被引用后才创建 PlaceHolderInfo，
 * 以便未被引用的占位符不会对连接顺序产生不必要的约束。
 */

typedef struct PlaceHolderInfo
{
	NodeTag		type;

	Index		phid;			/* PH 的 ID（在规划运行中唯一） */
	PlaceHolderVar *ph_var;		/* PlaceHolderVar 树的副本 */
	Relids		ph_eval_at;		/* 我们可以评估值的最低级别 */
	Relids		ph_lateral;		/* 包含的 LATERAL 引用的 relids（如果有的话） */
	Relids		ph_needed;		/* 值需要的最高级别 */
	int32		ph_width;		/* 估计的属性宽度 */
} PlaceHolderInfo;

/*
 * 这个结构描述了一个潜在可被索引优化的 MIN/MAX 聚合函数。
 * MinMaxAggPath 包含这些函数的列表，如果我们接受那个路径，
 * 列表将存储到 root->minmax_aggs 中，以便在 setrefs.c 中使用。
 */
typedef struct MinMaxAggInfo
{
	NodeTag		type;

	Oid			aggfnoid;		/* pg_proc 聚合的 Oid */
	Oid			aggsortop;		/* 其排序运算符的 Oid */
	Expr	   *target;			/* 我们正在聚合的表达式 */
	PlannerInfo *subroot;		/* 修改 "root" 以规划子查询 */
	Path	   *path;			/* 子查询的访问路径 */
	Cost		pathcost;		/* 获取第一行的估计成本 */
	Param	   *param;			/* 子计划输出的参数 */
} MinMaxAggInfo;

/*
 * 在运行时，PARAM_EXEC 槽用于在一个计划节点和另一个之间传递值。
 * 它们可以用于将值传递到子查询中（用于子查询中的外部引用），
 * 或从子查询中传递出来（用于子计划的结果），
 * 或从 NestLoop 计划节点传递到其内部关系中（当内部扫描
 * 被外部关系中的值参数化时）。
 * 规划器负责为生成的 PARAM_EXEC 参数分配不冲突的 PARAM_EXEC ID。
 *
 * 外部引用通过 root->plan_params 管理，这是一个 PlannerParamItems 列表。
 * 在规划子查询时，每个父查询级别的 plan_params 包含当前子查询所需的值。
 * 在 create_plan() 过程中，我们使用 plan_params 追踪必须从
 * NestLoop 计划节点的外部传递到内部的值。
 *
 * PlannerParamItem 表示的项可以是三种类型之一：
 *
 * 变量 (Var)：槽表示此级别的变量，必须传递下去，因为子查询
 * 有对此的外部引用，或者必须从 NestLoop 节点传递到其内部扫描。
 * 变量中的 varlevelsup 值将始终为零。
 *
 * 占位符变量 (PlaceHolderVar)：这与 Var 情况类似，除了
 * 该条目是一个包含表达式的 PlaceHolderVar 节点。PHV
 * 的 phlevelsup 将为 0，且被包含的表达式将被调整
 * 以匹配级别。
 *
 * 聚合表达式 (Aggref)（具有表示其参数的表达式树）：槽
 * 表示是某些子查询的外部引用的聚合表达式。Aggref 本身的
 * agglevelsup = 0，且其参数树被调整以匹配级别。
 *
 * 注意：我们检测重复的 Var 和 PlaceHolderVar 参数并将
 * 它们合并为一个槽，但我们不会为 Aggrefs 做这样的处理。
 * 重复消除的范围仅扩展到从一个查询级别传递到
 * 单个子查询的参数集合，或在单个查询级别中用于的
 * nestloop 参数集合。因此，PARAM_EXEC 槽被用于冲突目的的可能性
 * 是不存在的。
 *
 * 此外，PARAM_EXEC 槽也分配给代表来自子计划的输出的 Params
 * （那些子计划的 setParam 项的值）。这些 IDs 不需要通过
 * PlannerParamItems 跟踪，因为我们不需要任何
 * 重复消除或对表示的表达式的后续处理。
 * 相反，我们只需通过附加到 root->glob->paramExecTypes
 * 来记录槽号的分配。
 */
typedef struct PlannerParamItem
{
	NodeTag		type;

	Node	   *item;			/* Var、PlaceHolderVar 或 Aggref */
	int			paramId;		/* 分配的 PARAM_EXEC 槽号 */
} PlannerParamItem;

/*
 * 在进行 SEMI/ANTI/inner_unique 连接的成本估算时，
 * 在 nestloop 和 hash 连接中都需要一些修正因子，以考虑到
 * 执行器可以在找到与当前外部行匹配的行后立即停止扫描内部行的事实。
 * 这些数字仅依赖于所选择的外部和内部连接关系，而不是使用的
 * 特定路径，因此计算它们值得仅对每对关系计算一次，而不是
 * 对每条考虑的路径计算一次。这个结构由 compute_semi_anti_join_factors 填充
 * 并且必须传递给连接成本估算函数。
 *
 * outer_match_frac 是外部元组中预期至少有一个匹配的比例。
 * match_count 是对外部元组中预期至少有一个匹配的平均匹配数。
 */
typedef struct SemiAntiJoinFactors
{
	Selectivity outer_match_frac;
	Selectivity match_count;
} SemiAntiJoinFactors;

/*
 * 传递给 add_paths_to_joinrel 子程序的额外信息结构
 *
 * restrictlist 包含适用于此连接的所有 RestrictInfo 节点的限制子句
 * mergeclause_list 是此连接中可用的 mergejoin 子句的 RestrictInfo 节点列表
 * inner_unique 如果每个外部元组明显只匹配一个内部元组，则为 true
 * sjinfo 是关于选择性估算的特殊连接的额外信息
 * semifactors 如上所示（仅对 SEMI/ANTI/inner_unique 连接有效）
 * param_source_rels 是结果路径参数化的合适目标
 */
typedef struct JoinPathExtraData
{
	List	   *restrictlist;
	List	   *mergeclause_list;
	bool		inner_unique;
	SpecialJoinInfo *sjinfo;
	SemiAntiJoinFactors semifactors;
	Relids		param_source_rels;
} JoinPathExtraData;


/*
 * 各种标志指示可能的分组类型。
 *
 * 如果可以执行基于排序的分组实现，则应设置GROUPING_CAN_USE_SORT。 
 * 当使用分组集合时，如果对任何一个分组集合排序是潜在可用的，则这将为真，即使对所有集合都不可用。
 *
 * 如果可以执行基于哈希的分组实现，则应设置GROUPING_CAN_USE_HASH。
 *
 * 如果聚合的类型支持部分聚合（例如，不支持分组集合），则应设置GROUPING_CAN_PARTIAL_AGG。
 * 它不涉及并行安全性或合适路径的可用性。
 */
#define GROUPING_CAN_USE_SORT       0x0001
#define GROUPING_CAN_USE_HASH       0x0002
#define GROUPING_CAN_PARTIAL_AGG	0x0004

/*
 * 使用什么类型的分区聚合？
 *
 * PARTITIONWISE_AGGREGATE_NONE：未使用。
 *
 * PARTITIONWISE_AGGREGATE_FULL：单独聚合每个分区，并附加结果。
 *
 * PARTITIONWISE_AGGREGATE_PARTIAL：分别部分聚合每个分区，附加结果，然后完成聚合。
 */
typedef enum
{
	PARTITIONWISE_AGGREGATE_NONE,
	PARTITIONWISE_AGGREGATE_FULL,
	PARTITIONWISE_AGGREGATE_PARTIAL
} PartitionwiseAggregateType;

/*
 * 传递给create_grouping_paths子例程的附加信息结构
 *
 * 标志指示可能的分组类型。
 * partial_costs_set为真时，表示agg_partial_costs和agg_final_costs已被初始化。
 * agg_partial_costs给出部分聚合成本。
 * agg_final_costs给出最终成本。
 * target_parallel_safe为真时，表示目标是并行安全的。
 * havingQual给出在聚合后应用的qual列表。
 * targetList给出要投影的列列表。
 * patype是正在执行的分区聚合的类型。
 */
typedef struct
{
	/* 一旦设置后保持不变的数据。 */
	int			flags;
	bool		partial_costs_set;
	AggClauseCosts agg_partial_costs;
	AggClauseCosts agg_final_costs;

	/* 可能在各个分区之间有所不同的数据。 */
	bool		target_parallel_safe;
	Node	   *havingQual;
	List	   *targetList;
	PartitionwiseAggregateType patype;
} GroupPathExtraData;

/*
 * 传递给grouping_planner子例程的附加信息结构
 *
 * limit_needed为真时，表示我们实际上需要一个Limit计划节点。
 * limit_tuples是输出元组数量的估计上限， 
 * 		如果没有LIMIT或无法估计，则为-1。
 * count_est和offset_est是通过preprocess_limit()计算的LIMIT和OFFSET表达式的估计值 
 * 		（有关更多信息，请参见preprocess_limit()的注释）。
 */
typedef struct
{
	bool		limit_needed;
	Cardinality limit_tuples;
	int64		count_est;
	int64		offset_est;
} FinalPathExtraData;

/*
 * 出于速度原因，联接路径的成本估计分为两个阶段：
 * 第一个阶段试图快速推导联接成本的下限，然后检查这是否足以拒绝该路径。
 * 如果不够，我们再回来进行更精细的成本估计。第一个阶段
 * 填充一个JoinCostWorkspace结构，以其初步成本估计
 * 和可能的其他中间值。第二个阶段将这些值作为输入以避免重复工作。
 *
 * （理想情况下，我们会在cost.h中声明这个，但它在pathnode.h中也需要，
 * 所以最好把它放在这里。）
 */
typedef struct JoinCostWorkspace
{
	/* 初步成本估计——不得大于最终成本估计！ */
	Cost		startup_cost;	/* 获取任何元组之前的成本 */
	Cost		total_cost;		/* 总成本（假设获取所有元组） */

	/* 以下字段应视为costsize.c的私有字段 */
	Cost		run_cost;		/* 非启动成本组件 */

	/* cost_nestloop代码的私有字段 */
	Cost		inner_run_cost; /* 也被cost_mergejoin代码使用 */
	Cost		inner_rescan_run_cost;

	/* cost_mergejoin代码的私有字段 */
	Cardinality outer_rows;
	Cardinality inner_rows;
	Cardinality outer_skip_rows;
	Cardinality inner_skip_rows;

	/* cost_hashjoin代码的私有字段 */
	int			numbuckets;
	int			numbatches;
	Cardinality inner_rows_total;
} JoinCostWorkspace;

/*
 * AggInfo保存有关需要计算的聚合的信息。
 * 查询中的多个Aggrefs可以通过拥有相同的'aggno'值来引用相同的AggInfo，
 * 以便只计算一次聚合。
 */
typedef struct AggInfo
{
	/*
	 * 链接到该状态值所对应的 Aggref 表达式。
	 *
	 * 可以有多个相同的 Aggref 共享相同的每个聚合数据。这
	 * 指向它们中的第一个。
	 */
	Aggref	   *representative_aggref;

	int			transno;

	/*
	 * "shareable" 如果此聚合不能与其他聚合共享状态值，
	 * 则为 false，因为最终函数是读写的。
	 */
	bool		shareable;

	/* 最终函数的Oid或InvalidOid */
	Oid			finalfn_oid;

} AggInfo;

/*
 * AggTransInfo保存有关一个或多个聚合在查询中使用的过渡状态的信息。
 * 如果多个聚合具有相同的输入和相同的过渡函数，则它们可以共享相同的过渡状态。
 * 共享相同过渡信息的Aggrefs具有相同的'aggtransno'值。
 */
typedef struct AggTransInfo
{
	List	   *args;
	Expr	   *aggfilter;

	/* 状态转换函数的 Oid */
	Oid			transfn_oid;

	/* 序列化函数的Oid或InvalidOid */
	Oid			serialfn_oid;

	/* 反序列化函数的 Oid 或 InvalidOid */
	Oid			deserialfn_oid;

	/* 合并函数的 Oid 或 InvalidOid */
	Oid			combinefn_oid;

	/* 状态值的数据类型的 Oid */
	Oid			aggtranstype;
	int32		aggtranstypmod;
	int			transtypeLen;
	bool		transtypeByVal;
	int32		aggtransspace;

	/*
	 * 来自 pg_aggregate 条目的初始值
	 */
	Datum		initValue;
	bool		initValueIsNull;

} AggTransInfo;

#endif							/* PATHNODES_H */
